Page 1

F8I Р У С С К А Я Р Е Н И Н

Mtcrvsoft


т РУССКАЯ

Microsoft


Посвящается Джин, Тиму и Эрин — я живу благодаря людям, которых люблю.


Douglas J. Reiily

DESIGNING Microsoft*

ASP.NET

APPLICATIONS

Miavsoft Press


Д. Рейли

СОЗДАНИЕ ПРИЛОЖЕНИЙ Microsoft*

ASP.NET

Москва 2002

Н.РШШ Р Е П И Ц У


УДК 004.45 ББК 32.973.26-018.2 Р31

Р31

Рейли Д. Создание приложений Microsoft ASP.NET/Пер. с англ. — М.; Издательско-торговыи дом «Русская Редакция», 2002. — 480 с.: ил. ISBN 5-7502-0218-6 Эта книга познакомит вас с новой флагманской технологией Microsoft .NET Framework и общеязыковой средой исполнения CLR. Вы научитесь разрабатывать Web-приложения в среде ASP.NET, а также Webформы ASP.NET. Кроме того, вы узнаете об элементах управления а ASP.NET, в том числе о серверных HTML-элементах управления и элементах управления прове;эки входных данных, о пользовательских и нестандартных элементах управления, о том, как осуществляется взаимодействие с базой данных Microsoft SQL Server средствами ADO.NET и элементов управления с поддержкой данных. Отдельную главу автор посвятил особенностям создания XML Web-сервисов и их применения для организации общего доступа к данным в распределенных средах. Книга адресована программистам, которые ничего не знают о Webприложениях, но хотят заняться их разработкой, а также тем, кто уже знаком с ASP, но желает создавать более мощные и масштабируемые Web-приложения. Книга состоит из 10 глав, 2 приложений и предметного указателя. УДК 004.4.э ББК 32.973.26-018.2

Подготовлено к изданию по лицензионному договору с Microsoft Corporation, Редмонд, Вашингтон, США. ActiveX, JScript, Microsoft. Microsoft Press, MS-DOS, Visio Studio, Visual Basic, Visual C + + , Windows NT и Windows являются товарными знаками или охраняемыми товарными знаками корпорации Microsoft в США и/или других странах. Все другие товарные знаки являются собственностью соответствующих фирм. Все названия компаний, организаций и продуктов, а также имена лиц, используемые в примера*, вымышлены и не имеют никакого отношения к реальным компаниям, организациям, продуктам и лицам.

ISBN 0-7356-1348-6 (англ) ISBN 5-7502-0218-6

©

Оригинальное издание на английском языке, Дуглас Рейли, 2002

©

Перевод на русский язык, Microsoft Corporation, 2002

ее

Оформление и подготовка к изданию, издательско-торговый дом «Русская Редакция», 2002


Благоданости Введение

IX XII

ГЛАВА 1 ОСНОВНЫЕ СВЕДЕНИЯ О РАЗРАБОТКЕ В ASP.NET 1 Задача: разработка динамических Web-приложений Первое решение: CGI Преимущества CGI Недостатки CCI Второе решение: ISAPI-интерфейс Преимущества ISAPI Недостатки ISAPI Лучшее решение: ASP Преимущества ASP Недостатки ASP Новое решение: ASP.NET Заключение

3 4 7 7 8 12 13 14 19 20 24 25

ГЛАВА 2 УПРАВЛЯЕМЫЙ КОД И СРЕДА ИСПОЛНЕНИЯ COMMON LANGUAGE RUNTIME ... 27 Краткий обзор .NET Framework Промежуточный язык Microsoft Intermediate Language Компиляция «Just in Time» Управляемый код и данные О небезопасном коде Заключение

28 31 39 40 41 42

ГЛАВА 3 ОБЪЕКТЫ И ЯЗЫКИ .NET FRAMEWORK 43 Решение проблемы совместимости типов в .NET Размерные типы Ссылочные типы Встроенные ссылочные типы Классы в .NET Framework Основные сведения о Visual Basic .NET Старье — на помойку!

45 47 49 51 55 57 58


VI

Оглавление

Дорогу новому! Основные сведения о языке С# Различия C + + и С#

Что можно делать в С#, но нельзя — в Visual Basic .NET Заключение

61 70 70

73 78

ГЛАВА 4 ОСНОВЫ РАЗРАБОТКИ ПРИЛОЖЕНИЙ В ASP.NET 79 Здравствуй, мир ASP.NET! Пример наС# Пример на Visual Basic .NET Методика разработки приложений в ASP.NET Создание Web-приложений в Visual Studio .NET Visual Studio .NET и US-сервер Первая Web-страница, созданная в Visual Studio .NET Другие виды приложение ASP.NET XML Web-сервисы HTTP-обработчики и HTTP-модули Конфигурирование приложений Где хранится файл Web.config Раздел authentication Раздел authorization Раздел customErrors Раздел httpHandlers Раздел httpModules Раздел identity Раздел pages Раздел processModel Раздел sessionState Раздел trace Заключение

79 80 84 85 87 90 92 97 98 101 102 104 106 114 114 117 119 119 120 122 125 127 130

ГЛАВА 5 WEB-ФОРМЫ

132

Классическая архитектура ASP-программы Пример проверки корректности вводимых данных в формах ASP.NET Серверные элементы управления в ASP.NET и в HTML Элементы управления проверки правильности вводимых данных

Элемент управления RequiredFieldValidator Элемент управления CompareValidator Другие элементы проверки данных

133 137 141 143

144 150 155


Оглавление

VII

Несколько элементов управления, проверяющих одно поле Элемент управления ValidationSummary Сохранение информации о состоянии элементов управления

164 170

в ASP.NET

176

Программное управление серверными элементами управления 181 Заключение 195

ГЛАВА 6 СОЗДАНИЕ КОМПОНЕНТОВ ASP.NET.. 196 Хлопоты с компонентами Классы элементов управления ASP.NET Жизненный цикл элемента управления Создание пользовательских элементов управления Подготовка Web-страницы для преобразования в пользовательский элемент управления Преобразование Web-страницы в элемент управления Создание нестандартных элементов управления Пример нестандартного элемента управления Создание простого нестандартного элемента управления в Visual Studio .NET Более сложный нестандартный элемент управления Составной нестандартный элемент управления Внедрение элемента управления в пользовательский интерфейс Visual Studio .NET Расширение режима конструктора Заключение

196 200 202 205 206 210 223 223 227 230 237 244 250 253

ГЛАВА 7 РАСПРЕДЕЛЕНИЕ ФУНКЦИЙ МЕЖДУ СЕРВЕРОМ И КЛИЕНТОМ 254 Разработка клиентских сценариев Клиентские сценарии в ASP.NET Инициирование обращения к серверу из пользовательских элементов управления Создание расширенных элементов управления на стороне клиента Заключение

255 257

ГЛАВА 8 ВРЕМЯ ЗАНЯТЬСЯ ДАННЫМИ!

283

XML как универсальный язык данных Сравнение существующих решений форматирования данных с подходом на основе XML Идеален ли XML? Интерфейс Enumerator

263 268 281 284 285 286 287


VIM

Оглавление

Основные сведения об ADO.NET Краткий экскурс в ADO Различия между ADO и ADO.NET Использование ADO.NET в ASP.NET Классы SqICIient и OleDb Преобразование данных в XML-текст Заключение

ГЛАВА 9 ДАННЫЕ И ФОРМЫ ASP.NET Доступ к данным с применением ASP-форм Доступ к данным с использованием форм ASP.NET Серверный элемент управления DataCrid Изменение DataGrid в конструкторе Visual Studio .NET Модификация DataGrid в Visual Basic .NET Серверный элемент управления Repeater Основы работы с элементом управления Repeater Создание страниц ввода данных Создание пользовательского интерфейса Обработка ввода данных Заключение

ГЛАВА 10 XML WEB-СЕРВИСЫ

292 292 293 296 320 321 325

326 327 329 330 332 339 348 350 362 364 373 386

388

Стандарты XML Web-сервисов Создание простого XML Web-сервиса Расширение и тестирование XML Web-сервиса Использование свойств атрибута WebMethod Обращение к простому XML Web-сервису XML Web-сервисы и программы командной строки Реальный XML Web-сервис: публикация статей Параметры безопасности Создание и тестирование XML Web-сервиса Обращение к XML Web-сервису Возможные усовершенствования Заключение

389 391 393 399 401 410 413 414 415 420 424 426

Приложение А

427

Приложение Б

439

Предметный указатель

451

Об авторе

456


Я принадлежу к тем немногочисленным счастливчикам, которым удалось выжить после того, как у них был обнаружен рак печени. Такая беда приключилась, когда я работал над этой книгой, и параллельно с литературной деятельностью мне пришлось пройти курс лечения. То, что сейчас, почти четыре года спустя, я пишу эти строки, я приписываю фортуне, отличным технологиям и хорошим людям. Первый в ряду хороших людей — доктор Хэнс Гердес (Hans Gerdes) из Ракового центра памяти Слоуна Кэттеринга (Memorial Sloan Kettering Cancer Center) и его секретарь Джоанн Бут-Песантес (Joanne Booth-Pezantez). Д-р Гердес не стал ограничиваться простой отговоркой типа «мы не знаем, что это за пятно на печени Дуга». Для меня он больше чем доктор — он стал тем, к кому я могу обратиться за советом и поддержкой в том ужасе, которым для меня стал наследственный аденоматозный полипоз корень всех проблем со здоровьем з моей семье. Подробности об этом и других наследственных раковых заболеваниях кишечника вы можете узнать на сайте http://www.hereditarycc.org. Я должен также поблагодарить двух Фредов [Фреда Стодолак (Fred Stodolak) и Фреда Палиани (Fred Paliani)], Джима Хоффмана (Jim Hoffman), Рича Явароне (Rich lavarone), Тару О'Нейл (Тага O'Neill) и Джейсона Надала (Jason Nadal) из Общества любителей гольфа США. «Фреды» создали для меня рабочую обстановку: я не только получил возможность работать с чудесными технологиями, но мог одеваться как вздумается, не следуя корпоративным стандартам з одежде. Они также любезно разрешили мне использовать пару статей с сайта Американского общества любителей гольфа (http://www.golfsociety.com) в качестве примеров в глазе 10. Джим оказался самым лучшим боссом для парня типа меня, который предпочитает «закопаться» в подвале и ковыряться с компьютерами. Джим просмотрел некоторые из глав, и его откровенная оценка того, что важно, а что нет, сильно помогла мне в работе. Рич и Джейсон оказали мне неоценимую помощь, консультируя по вопросам JavaScript. Тара чудесным


X

Благодарности

образом преобразила мою фотографию — теперь ее без стеснения можно публиковать в разделе «Об авторе». Впрочем, можете мне поверить: ей не слишком уж пришлось повозиться с моим изображением! Спасибс всем. В свободное время я много делаю для агентства «Система здравоохранения им.Св.Варнавы» (St. Barnabas Health Care System, SBHCS). С позволения Кэйти Коллинс (Kathy Collins) и Рича Уитли (Rich Wheatley) мне удалось четыре с небольшим года проработать над интереснейшими проектами уже после того, как я перестал работать в SBHCS на полной ставке. В SBHCS я получил возможность разрабатывать программы в замечательной среде, создавая «крутые» системы для одной из крупнейших в штате интрасетей. Кроме Рича и Кэйти я также тесно сотрудничаю с Дарси Киндред (Darcy Kindred) (она настоящая богиня интерфейса), Райаном Гримом (Ryan Crim) и Джоанн Гибсон (Joanne Gibson) всех и не упомнишь, Спасибо за ваше терпение. Особую благодарность хотелось бы выразить сотрудникам центра вызовов в подразделении поведенческих дисциплин в SBHCS за то, что они терпели все задержки в реализации изменений их системы из-за моего перегруженного графика во время работы над этой книгой. Сьюзен Уоррен (Susan Warren) из Microsoft оказала неоценимую помощь, когда я задерживал очередную главу, потому что примеры отказывались работать. Ее терпение при поиске моих ошибок и ее готовность докапываться до сути, когда возникали серьезные неполадки, неоценимы. Сьюзен, а также Скотт Гутри (Scott Guthrie) и Роб Ховард (Rob Howard) очень помогли мне и все авторам, пишущим об ASP.NET, в начале работы, когда мы собрались на первой, посвященной ASP.NET встрече — кажется, это было так давно! На той встрече, на практическом занятии, я оказался рядом с Эндрю Дьюти (С. Andrew Duthie) из компании Craymad Enterprises, С тех пор мы переписываемся и выражаем друг другу сочувствие по электронной почте и лично на различных конференциях. Эндрю вот-вот закончит свою книгу no ASP.NET, и я настоятельно рекомендую ее вам. Думаю, Эндрю — самый искренний из известных мне сторонников технологий Microsoft. В числе людей, к которым я обращался за консультацией по технической части, да просто за советом, хотелось бы упомянуть Еда Колоси (Ed Colosi), Тома Диньяна (Tom Dignan), Майкла Заккарди (Michael Zaccardi)


Благодарности

XI

и Сью Шоу (Sue Shaw). Клодетт Мур (Claudette Moore), мой агент, работала над этой книгой больше чем, над некоторыми другими. Спасибо за содействие в выпуске книги в свет и за помощь в приведении моих мыслей в порядок. Написание книги в Microsoft Press отличается от аналогичной работы в других издательствах. Но в моем случае эта работа отличалась даже от обычных процедур Microsoft Press. Это не совсем та книга, которую я первоначально планировал, и по большому счету виной тому огромный успех ASP.NET даже з бета-версии. По этой причине было важно выпустить книгу быстро, без промедления, хотя приходилось очень оперативно вносить правку, Все редакторы, которые работали над этой книгой: Салли Стикни (Sally Stickney), Дженнифер Харрис {Jennifer Harris), Дэвид Кларк (David Clark) и Роберт Лайон (Robert Lyon) — проявили себя наилучшим образом даже в условиях ужасающе сжатых сроков, обычных неполадок в бета-версии программного продукта и постоянно возникающих у меня идей, что «обязательно нужно добавить еще то и это». В таких ситуациях Роберт не раз защищал меня от меня. Спасибо! Конечно, жизнь в одной семье с автором, когда он пишет книгу, нелегка. Моя дочь Эрин позаботилась, чтобы я не забывал, что в жизни есть кое-что еще кроме книги — она познакомила меня с музыкой Ани ДиФранко. Сейчас я жду не дождусь концерта этой певицы! Мой сын Тим на протяжении всего лета напоминал мне о необходимости физических нагрузок, и я регулярно следовал его советам. Спасибо за пешие и велосипедные прогулки! Моя жена Джин, с которой мы вместе уже 23 года, все это время была «стержнем» нашего семейства. Когда я отвлекался на что-нибудь, именно Джин настаивала, чтобы в первую очередь делалось то, что дОлжно. В нашем случае это были почти ежедневные контакты с компаниями по страхованию здоровья. Уже за одно это она заслужила место в раю. Конечно, много всего и помимо этого. Помните эту фразу: «..в радости и печали, в болезни и здравии...»? Джин понимает ее буквально! В нашей жизни было больше печали и болезни, чем радости и здравия, но я всегда знал, что есть человек, который будет рядом в трудную минуту, никогда не бросит и не разочаруется. Джин, я желаю нам быть вместе по крайней мере следующие сто лет!


When I look down, I miss all the good stuff. And when I look up, I just trip over things. Ani DiFranco (Опускал глаза, я теряю из виду много интересного, А когда поднимаю взгляд, то «скольжу» поверх вещей. Ани ЛиФранко) Когда больше года назад на конференции авторов я впервые услышал об ASP.NET корпорации Microsoft, я подумал, что все это кажется слишком хорошим, чтобы быть правдой. Неужели теперь мы сможем создавать Web-страницы, основанные на скомпилированной программе, используя один из «крутейших» объектноориентированных языков? Использовать настоящие переменные с типами и всем остальным? Создавать серверные компоненты в среде .NET, инкапсулируя все нужные моему приложению функции и не заботясь о развертывании СОМ-компонентов? Использовать специальные проверочные компоненты (validator), чтобы чудесным образом проверять вводимые пользователями данные как на клиенте, так и на сервере? Как я уже сказал, это звучало слишком хорошо, чтобы быть правдой. На самом деле возможности ASP.NET оказались даже больше. В ASP.NET сильно упрощен процесс разработки Web-приложений. Раньше при создании очень сложных ASP-страниц я всегда старался перенести все функции в базу данных, так как ее существенно проще развертывать, чем россыпь ASP-файлов на всех серверах кластера. Теперь я больше не ломаю над этим голову, и вам этого делать не придется! Естественно, что новые функции принесли с собой определенное усложнение. Есть одна вещь, которая намного сложнее работы со всеми новшествами Microsoft .NET Framework, — это попытка описать их. При работе над большинством глав этой книги я чувствовал себя так же, как автор строк, вынесенных в эпиграф. Ковыряясь с рутинными вещами, забываешь, каким классным делом занимаешься, но, восхищаясь возможностями, легко споткнуться на деталях.


Введение

XIII

Я попытался здесь рассказать о деталях, которые следует знать, чтобы что-то сделать, но среда .NET Framework огромна. В ней тысячи классов. Часто я отсылаю читателя к документации по .NET Framework з MSDN, да и вам настоятельно рекомендую пользоваться этим источником. Если нужно что-то делать со строками, поищите в MSDN информацию о классе System.String. Если требуется выполнить операции с файлами, поинтересуйтесь пространством имен System.lO. Я старался не дублировать информацию, которая есть в MSDN, кроме тех случаев, когда приходилось исчерпывающе описывать задачу (а эту книгу можно считать в некотором роде учебным пособием), начиная с азов и последовательно переходя к задачам «реального» мира и их решениям,

Кому адресована эта книга ASP.NET предоставляет возможности разработчикам, в настоящее время работающих с ASP, создавать более мощные и масштабируемые Web-приложения, а программистам, которые ничего раньше не знали о Web-приложениях, начать их разрабатывать. Из-за такой разнородности аудитории предполагаемых читателей этой книги мне приходится учитывать интересы и тех, кто не может похвастаться огромным опытом Web-разработки. Специально для них в конце книги я поместил приложение Б с кратким справочником по языку HTML. Поскольку в ASP.NET читатели с равной вероятностью могут использовать Microsoft Visual Basic .NET и С#, я не стараюсь ориентироваться исключительно на какой-то один из этих языков в ущерб другому. Как VB-программисты, так и разработчики, пишущие на C++ и плохо знакомые с ASP.NET, вполне поймут исходные тексты примеров. В книге есть примеры как на Visual Basic .NET, так и на С#. В тех немногих случаях, когда различия в языках программирования существенны, я показываю пример на обоих языках или чаще просто указываю на имеющиеся различия. Знание .NET Framework — вот что вам прежде всего потребуется для программирования в ASP.NET.

Структура книги Глава 1 посвящена основам разработки в ASP.NET. Чтобы облегчить понимание материала главы, я познакомлю вас с историей технологий, которые предшествовали ASP.NET. Если вы плохо


XIV

Введение

знакомы с разработкой Web-приложений, из этой главы вы почерпнете много полезных сведений. В главе 2 рассказывается об управляемом коде и общеязыковой среде исполнения CLR (Common Language Runtime). В .NET разработка прикладных программ коренным образом отличается от создания традиционных \Л/1п32-приложений. Если вы еще не занимались программированием для .NET (а на настоящий момент мы все з этом деле новички), эта глава позволит зам быстро вникнуть в суть дела. Вечная головная боль разработчиков, пишущих на Visual Basic и C+ + (особенно тех, кто применяет оба языка), — отсутствие у этих языков общих типов. Кроме того, VB-разработчикам дополнительные сложности создает отсутствие удобного доступа ко всем возможностям API-интерфейса Win32. В главе 3 я расскажу о том, как обе указанные проблемы реиены в .NET Framework. Глава 4 посвящена программированию в среде ASP.NET. На примерах как на Visual Basic.NET, так и на С#, я покажу, как создавать приложения ASP.NET в Visual Studio .NET. Это очень удобная среда создания приложений ASP.NET, разработка в ней сильно отличается от программирования в более простых редактозах, но иногда нужны и они. В главе 5 описано создание Web-форм ASP.NET. Все приложения ASP.NET базируются на Web-формах. Разработчикам, не знакомым с ASP-NET, но прекрасно знающим ASP, я рекомендую познакомиться с методикой разработки в ASP.NET, так как она сильно отличается от ASP. Из главы 5 VB-разработчики узнают обо всех тех особенностях, которые отличают Web-формы, от форм, к которым они привыкли. В главе 6 описаны новые способы разработки компонентов. Помимо создания пользовательских элементов управления, в ASP.NET предоставляется возможность программировать компоненты на тех же языках, которые применяются для создания Web-форм, или же на языках, не обязательно поддерживаемых .NET Framework. В главе 7 рассказывается о создании компонентов, в которых комбинируются функции сервера и клиента, так как в одних ситуациях разумно выполнять задачу на клиенте, а в других — на сервере. Из главы 7 вы узнаете, как создавать компоненты и распределять функции между клиентом и сервером. Глава 8 посвящена технологии ADO.NET. Ни один экскурс в ADO.NET нельзя считать полным без рассказа об XML, именно об этом и пойдет здесь речь. В главе 9 описаны способы приме-


Введение

XV

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

О вспомогательных материалах Исходные тексты и другие вспомогательные материалы вы можете загрузить с Web-сайта издательства «Русская Редакция» (http://www.rusedit.ru}. Все примеры проверены во второй бетаверсии Microsoft Visual Studio .NET в базовой конфигурации системы, состоящей из операционной системы Microsoft Windows 2000 Server SP2 и Web-серзера IIS (Information Internet Services). В примерах глав 8, 9 и 10 демонстрируется доступ к базе данных, поэтому для них потребуется сервер Microsoft SQL Server 2000. В главах 9 и 10 используется база данных GolfArticles, она есть в комплекте вспомогательных материалов. В каждой папке с примерами есть файл Readme, в котором содержатся инструкции по установке и работе с примерами. Не забудьте ознакомиться с ними до начала работы. Для работы примеров этой книги необходимо следующее: • Microsoft Visual Studio .NET версии бета 2 или более поздней; •

ОС Microsoft Windows 2000 или Microsoft Windows XP;

• Microsoft SQL Server 2000 (для примеров из глав 8, 9 и 10).

Извинения автора Я предпринял все усилия, чтобы обеспечить точность информации, содержащейся в этой книге и во вспомогательных материалах. Несмотря на всю помощь сотрудников Microsoft и Microsoft Press, вина за любые ошибки или пробелы в конечном итоге лежит на мне. ASP.NET — это новая технология, и на момент написания этой книги она есе еще немного менялась. Роберт Лайон (Robert Lyon), технический редактор этой книги, неоднократно замечал:


XVI

Введение

«То, что было предельно ясно и однозначно выражено з одной версии документации no .NET, оказывалось частично или полностью неверно в следующей». Трудности такого рода всегда возникают при написании книги о программном продукте, который находится на этапе бета-тестирования. Последние версии продукта стали намного устойчивее, а значит, и документация уже практически не меняется. В новых версиях уже редко корректируют поведение — обычно лыиь более подробно описывают тот или иной раздел. Таким образом, я опасаюсь, что с выходом финальной версии в моем коде обнаружится пара-тройка шероховатостей и нестыковок или некоторые темы потребуется более детально объяснить. Буду очень признателен за сообщения о найденных ошибках. В следующих изданиях я их обязательно исправлю, а пока я собираюсь создать страницу на своем Web-сайте, где буду регулярно информировать об исправлениях и найденных неточностях. Спасибо за то, что вы выбрали эту книгу! Дуглас Рейли doug@Progr3mmingASP.NET http://www.ProgrammingASP.NET

Материалы издательства Microsoft Press Издательство Microsoft Press постоянно обновляет список исправлений и дополнений к своим книгам, он доступен на Web-узле http ://mspress .microsoft.com/support/. Если у вас есть комментарии, вопросы или идеи по этой книге, направляйте их в издательство Microsoft Press no одному из перечисленных ниже адресов. Почтовый адрес: Microsoft Press Attn: Designing Microsoft ASP.NET Applications Editor One Microsoft Way Redmond,WA 98052-6399 Электронная почта: MSPINPUT@MICROSOFT.COM Учтите, что по указанным почтовым адресам техническая поддержка не предоставляется.


Глава 1

овные сведения О

Г"% ^1 *!> •"% ^ \П/"ЪПГЕ^Л! pd JpclO01 Kc

eASP.NET Эта книга посвящена ASP.NET, но вы не сможете з полной мере оценить эту технологию, не знал, как изменялись процессы разработки Web-приложений на протяжении последних лет. В этой главе я вкратце познакомлю вас с традиционными способами разработки Web-приложений: с языком HTML, CGI (Common Gateway Interface), ISAPI (Internet Server Application Programming Interface) и ASP-страницами (Active Server Pages). Хотя эти проверенные годами способы вполне годятся для создания Web-приложений, в ASP.NET создание масштабируемых динамических Web-приложений выполнять гораздо проще. ,..И был создан язык разметки гипертекста (Hypertext Markup Language, HTML). И это было хорошо... В действительности о нем и сейчас вряд ли кто отзовется плохо. Это язык разметки, то есть он описывает, как представлять текст и графические элементы. HTML-документы содержат тэги, которые управляют отображением элементов документа, — ключевые слова (часто с атрибутами), обрамленные знаками «меньше» и «больше» (<>) (их еще называют угловыми скобками). Например, тэг <BODY> указывает на начало тела документа. Большинству (хотя и не у всем) тэгам соответствует парный закрывающий тэг, в котором эле-


2

Глава 1

менту предшествует правая наклонная черта (/), например </BODY>. Тэги, применяющиеся для разрыва строк (<BR>) и обозначения начала абзацев (<Р>), обычно не закрываются парными им тэгами. Более подробно об HTML рассказано в приложении Б. Языка HTML вполне достаточно для отображения статической информации, которая редко изменяется и не зависит от того, кто ее просматривает. На протяжении многих лет после появления HTML такого способа статичного отображения информации было вполне достаточно. Вы себе не представляете, как трудно в начале 1990-х протекала совместная работа над документами. В то время документы создавалась в форматах WordPerfect, WordStar или Word Microsoft. Дою/менты, созданные в одной программе, практически всегда оказывались недоступными из других программ, и в качестве общего языка применялся формат ASCII — он позволял передавать содержимое документа, но за это приходилось платить полной потерей форматирования. HTML предоставил пользователям возможность просматривать документы независимо не только от типа используемого приложения-редактора, но даже от типа компьютера, причем пользователи могли находится за тысячи километров друг от друга. В настоящее время HTML обычно применяется для публикации документов з Интернете или локальных интрасетях в формате, доступном практически любым пользователям. HTML и протокол передачи информации в формате HTML HTTP (Hypertext Transfer Protocol) предоставили дополнительные преимущества. HTTP-протокол очень «легок», поэтому он обеспечил эффективное использование весьма ограниченной в то время пропускной способности каналов связи. Мой компьютер теперь подключен к Web no быстрому кабельному модему, но до этого многие годы я просматривал Web-страницы, пользуясь модемом со скоростью 23,8 или 56 кбит/с. Но даже таких скоростей вполне хватало для получения HTML-документов. HTTP не только нетребователен к пропускной способности, но и несильно нагружает сервер, который хранит и предоставляет HTMLстраницы клиентам по запросу.


Основные сведения о разработке в ASP.NET

3

Задача: разработка динамических Webприложений Со временем стало понятно, что кроме простого представления страниц HTTP можно использовать для отображения динамической информации. Обратите снимание, что я не считаю динамическим информационным наполнением (dynamic content) анимированные значки и танцующих домашних животных, которые встречаются на многих Web-страницах. Обычно подобная анимация создается с применением JavaScript-сценариев, выполняемых браузером пользователя. Под динамическим наполнением я подразумеваю информацию, содержание которой определяется тем, кому она предназначена, и которая отличается от просмотра к просмотру. Динамическое содержимое позволяет обеспечить двусторонний обмен информацией. Используя формы на Webстранице, пользователь получает возможность запрашивать информацию различного вида. Например, указав транспортный идентификатор партии товара, он может подробнее узнать, где она находится в данный момент. Естественно, что обмен пользователя и сервера состоит не только из форм и индивидуализированной информации; есть еще файлы-cookies — небольшие кусочки информации, которые размещаются на пользовательской машине и позволяют идентифицировать пользователя в процессе сеанса связи или при следующем посещении Web-узла.

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


4

Глава 1

В середине 1990-х многим компаниям пришлось снижать совокупную стоимость владения (total cost of ownership, TCO). Вместе с тем традиционный «толстый» клиент с противоречивыми динамически подключаемыми библиотеками (DLL) и параметрами реестра стал составлять большую часть ТСО. Поэтому многие компании увидели з Web-приложениях способ быстро развернуть на предприятии критически важные приложения при минимальном переконфигурировании клиентских машин. Например, когда я пишу эти строки, я использую несколько приложений, развернутых с применением электронной почты, — пользователям просто предоставили URL-адреса и начальные инструкции по работе в системе. Регистрируя нового пользователя, администратор инициирует автоматический ответ по электронной почте; таким образом, сокращаются усилия по развертыванию приложения. Задача управления всем динамическим наполнением стала намного сложнее, чем простое размещение статических HTML-документов в нужных папках и предоставление пользователям возможности просматривать их. В настоящий момент известно несколько способов предоставления динамической информации — я расскажу о них далее. V каждого из них свои плюсы и минусы.

Первое решение: CGI CGI-интерфейс (Common Gateway Interface) — одно из первых решений, созданных длг доставки динамической Web-информации, он до сих пор очено популярен в мире UNIX. CGI-приложения — это программы, исполняемые на Web-сервере и обычно используемые для предоставления динамической Web-информации. В листинге 1-1 показан пример простого консольного CGIприложения, которое отображает в браузере строку «Hello CCI World». // SayHelloCGI, cpp: npoc"oe CGI-приложение

ftlnclude "stdafx.h" ftinclude <stdio. h> int main(int argc, char* argv[])


Основные сведения о разработке в ASP.NET

printf("HTTP/1,0 200 OK\r\nContent-Type: text/html\r\n\r\n"}; printf("<HTML>\r\n<HEAD>"); printf('XTITLE>Hello CGI World</TITLEX/HEAD>\r\n") ; printf("<BODY>\r\n<CENTER><H3>Hello CGI World</H3X/CENTER>"); printf("<BR>\r\n</BODY>\r\n"); printf("</HTML>\r\n"); return 0;

Листинг 1-1. Простое CGI-приложение Эта очень простая CGI-программа выводит и заголовок, и HTMLтекст, которые отображаются з браузере, как показано на рис. 1 -1 . Первая функция printf возвращает минимально необходимую заголовочную информацию. Первый заголовок информирует о версии HTTP (HTTP/1.0) и предоставляет код, указывающий на успех операции (200 ОК). Следующая строка сообщает тип информации — в данном случае это text/html. На основе таких сведений браузер узнает, как интерпретировать данные. Например, если вместо text/html указать application/msword, браузер интерпретирует информацию как документ в формате Microsoft Word или RTF, а не HTML. Перечень заголовков завершается двумя парами знаков «возврат каретки/перевод строки», которые сигнализируют о завершении заголовков. Далее следует обычная информация в формате HTML. Hello CGI World Mi, rowjft tiAer

Links

Hello CGI World

::

. '

Рис. 1-1. Отображение в окне браузера страницы, полученной в результате работы программы, показанной в листинге 1-1


Глава 1

О консольных приложениях Хотя программу из листинга 1-1 можно скомпилировать как стандартное 16-разрядное приложение MS-DOS, я скомпилировал ее как полноценное 32-разрядное консольное приложение. Как видно из рисунка, в процессе исполнения это приложение напоминает старое, доброе MS-DOS-приложение текстового режима, но на самом деле это настоящее 32разрядное приложение, способное обращаться практически к любым \Мп32-функциям, загружать DLL-библиотеки и т. п. Сегодня есть и лучшие способы создания приложений на скорую руку, но некоторые приложения (особенно утилиты командной строки) лучше работают как консольные. Приложения-службы (те, что работают, даже когда ни один пользователь не вошел в систему) — это консольные приложения, которые в своей работе используют пару-тройку специальных API-функций.

В стандартных консолы-ых приложениях под стандартным устройством ввода (standard input} подразумевается клавиатура, а под стандартным устройством вывода — экран. CGI-приложение считывает данные со стандартного устройства ввода и записывает информацию на стандартное устройство вывода. В большинстве операционных систем стандартные устройства можно переназначить, именно это происходит с программой, выполняемой как CGI-приложение.


Основные сведения о разработке в ASP.NET

7

Преимущества CGI CGI-программа способна выполнять практически все, что вы пожелаете, — получать доступ к базам данных, считывать файлы, работать с системным реестром и все остальное, что обычно делает \Л/т32-программа.2 В листинге 1-1 используется C/C+ + , но ничто не мешает воспользоваться любым другим языком программирования или средой разработки и создать консольное приложение, считывающее со стандартного устройства ввода и записывающее в стандартное устройство вывода. В мире UNIX для создания CGI-программ применяется Perl, а среди поклонников Win32 популярна среда Delphi Borland, которая явно поддерживает СС1-приложения, предоставляя классы, управляющие чтением и записью в стандартные устройства. Если преподать программистам, имеющим опыт создания программ текстового режима, азы HTML, высока вероятность, что они сразу смогут создавать почти приличные CGI-программы. Такие программы просто тестировать, а цикл «программирование — тестирование —отладка» довольно прямолинеен. Достаточно настроить компилятор на сохранение исполняемого файла в нужном каталоге — и можно тестировать приложение, открывая соответствующую страницу в браузере и при необходимости возвращаясь к редактору, чтобы внести изменения.

Недостатки CGI Чтобы понять недостатки CGI, следует поближе познакомиться с тем, что происходит при исполнении CGI-программы. Например, для вызова приложения, показанного в листинге 1-1, можно использовать следующий URL-agpec: http://localhost/sayhellocgi/sayhellocgi.exe Возможен один из двух сценариев поведения сервера Microsoft Internet Information Services (IIS) при вызове этого URL-адреса: IIS либо предложит загрузить программу SayHelloCGI.exe с виртуального каталога sayhellocgi на локальную машину, либо выполнит ее. Естественно, нам нужно, чтобы программа выполнялась, для этого следует предоставить разрешение на исполнение в указанном виртуальном каталоге. (Подробнее о настройке таких разрешений рассказано в приложении А.)


8

Глава 1

При наличии соответствующих разрешений после ввода URLадреса SayHetloCCI.exe исполняется с информацией, поступившей через стандартное устройство ввода, и отправляет информацию, предназначенную стандартному устройству вывода, в браузер. Если G заголовках содержится ошибка (например, если отсутствует вторая паре, знаков «возврат каретки/перевод строки», завершающая заголовочный блок), одни браузеры просто проигнорируют текст, другие отобразят окно с сообщением об ошибке и выведут текст на стандартное устройство вывода. Выполнив свою задачу, CGI-программа завершает работу. Модель CCI хороша тем, что после завершения работы CGI-npoграмму можно так же, как и любую другую программу, изменять или удалять; однако эта возможность является и недостатком CGI. При исполнении CGI-программа загружается, а по завершении работы полностью удаляется из памяти. Много ресурсов тратится на создание и удаление процессов. Создание процесса — более ресурсоемкая операция по сравнению, скажем, с простым считыванием HTML-файла. В конечном счете, подобное создание и удаление процессов сильно снижает производительность, а также перегружает ресурсы. Когда 100 клиентов одновременно обращаются к одной CGI-программе, в памяти создается 100 экземпляров этой программы. Ресурсы Web-сервера активно «пожираются», что отрицательно сказывается на масштабируемости. По мере развития Web-узлов от простых вспомогательных информационных каналов до крупных критически важных средств электронной коммерции становилось все более ясно, что необходимо решение, лишенное недостатков CGI.

Второе решение: ISAPI-интерфейс Microsoft разработала для разработчиков новый способ создания масштабируемых приложений, который призван устранить присущие CGI проблемы с производительностью и масштабируемостью. Эта высокопроизводительная альтернатива называется ISAPI (Internet Server Application Programming Interface — API-интерфейс Интернет-сервера). Вместо исполняемых файлов в ISAPI используются DLL-библиотеки, что позволяет повысить производительность и масштабируемость.


Основные сведения о разработке в ASP.NET

9

В ISAP! существуют два типа DLL-библиотек: ISAPI-расширения (ISAPI extensions) и lSAPI-фильтры (ISAPI filters). Первые явно указываются в URL-адресе, отправляемом на US-сервер, например, так: http://localhost/sayhelloisapi/sayhelloisapi.dll ISAPl-расширение можно вызывать с параметрами, которые позволят одному компоненту выполнять разные задачи. Так же как в случае с CGI, в каталоге следует предоставить разрешения на исполнение, в противном случае DLL загружается на клиент, а не исполняется на сервере. ISAPI-расширения обычно применяются для обработки клиентских запросов и возвращения ответа в формате HTML. Это очень похоже на схему работы CGI-npoграмм. В CGI нет прямого аналога функциям, выполняемым ISAPI-фильтрами. ISAPI-фильтры никогда не вызываются явно — US-сервер обращается к ним в ответ на определенные события з процессе выполнения запроса: • на завершение обработки сервером предоставленных клиентом заголовков; • на завершение сервером процедуры аутентификации клиента; • на сопоставление сервером логического URL-адреса физическому; • до начала пересылки «сырых» данных от клиента на сервер; • на завершение пересылки «сырых» данных от клиента, но до начала их обработки на сервере; • на регистрацию информации сервером в журнале; • на завершение сеанса. Как любой другой фильтр, ISAPI-фильтр должен запрашивать лишь нужные ему уведомления и обрабатывать их максимально быстро. Чаще всего ISAPI-фильтры применяются для поддержки особых процедур аутентификации. Другое применение — модификация HTML-текста перед отправкой клиенту, например для изменения цвета фона страниц. Поскольку ISAPI-фильтры не столь популярны, как ISAPI-расширения, я ограничусь только этими сведениями. Более подробную информацию об ISAPI-расши-


Глава 1 рениях вы найдете в моей книге «Inside Server-Based Applications.» (Microsoft Press, 1999). В ISAPI определено несколько точек входа в функции, экспортируемые из DLL. Эти точки входа позволяют IIS загружать DLL, вызывать реализованные в ней функции, передавая при необходимости нужные параметры, и получать данные для обратной отправки в браузер. ISAPI требует наличия реализации по крайней мере двух функций точки входа — GetExtensionVersion и HTTPExtensionProc. ISAPI-расширения часто создаются с использованием ISAPI-классов библиотеки MFC (Microsoft Foundation Class Library). Это значительно упрощает разработку ISAPI-расширений. Например, если в среде Microsoft Visual C++ 6.0 в диалоговом окне New Projects выбрать вариант ISAPI Extension Wizard, откроется первая страница мастера создания ISAPI-расширений (рис. 1 -2). Если вам нужно только ISAPI-расишрение, этого вполне достаточно — нажмите кнопку Finish, и мастер создаст все необходимые ISAPIрасишрения файлы. В описанном примере я назвал ISAPl-pacuiuрение SayHellolSAPI.

W .бвпиа!в э Saver EatensiofVobjeel: Extension ~\ъ*1 Yarns' jCSavHeflolS API Extension ЕОДПЗДП Inscription ]SavHellolSAPI Exlensicn How would w ч KKS Jj u?e the MFC libracy?

Рис, 1-2. Первый этап создания ISAPI-расширения в Visual C + + 6.0


Основные сведения о разработке в ASP.NET

11

Одна из созданных мастером функций называется Default. Чтобы продублировать функции, выполняемые CGI-программой (листинг 1-1), я изменил созданную мастером реализацию Default (листинг 1-2). /////////////////////////////////////////У////////////////////// // CSayHelloISAPIExtension command handlers void CSayHelloISAPIExtension::Default(CHttpServerContext- pCtxt) { StartContent(pCtxt); WriteTitle(pCtxt); *pCtxt « _T("<CENTERXH3>Hello ISAPI World</H3x/CENTEfl>"); *pCtxt « _T(" \r\n"); EndContent(pCtxt);

} Листинг 1-2.

Функция Default в простом ISAPI-расширении

Обратите внимание, что вся отображаемая в окне браузера информация записывается явно. Реализация по умолчанию функции StartContent создает открывающие тэги <HTML> и <BODY>. Реализация по умолчанию функции WriteTitle вызывает GetTitle и затем обрамляет полученный текст тэгами <TITLE> и </TlTLE>. Я заменил реализацию по умолчанию функции GetTitle и задал собственный заголовок, как в примере с CGI в листинге 1 -1, следующим образом: LPCTSTR CSayHelloISAPIExtension::GetTitle() const I return "Hello ISAPI World"; I Функция EndContent расставляет закрывающие тэги </HTML> и </BODY>. После компиляции DLL-библиотеки lSAPI-расширения и создании соответствующего виртуального каталога в IIS, DLL-файл можно скопировать в каталог и выполнить, введя корректный URL-agрес. В окне браузера отобразится страница, похожая на показанную на рис. 1-3.


Глава 1

«J hl^:/fl«alhost/saYhdloisapi/s.jyh*lloi5api.<*

Рис. 1-3. Страница, созданная программой SayHellolSAPI, в браузере

Преимущества ISAPI В ISAPI устранены многие из недостатков CGI-приложений. В отличие от CGI-приложений, процессы которых создаются и уничтожаются с каждым запросом, код ISAPI-расширения обычно загружается лишь раз и на все время работы сервера (если только память не понадобится для других целей, что случается нечасто!. В качестве дополнительного преимущества стоит упомянуть то, что ISAPI-приложение выполняется в процессе US-сервера, обеспечивая ISAPI-расширению лучшую связь с IIS. В последних версиях IIS администратору предоставляется больший контроль над там, в какой области памяти исполняются отдельные приложения, Обычно новые или сомнительные приложения работают в отдельном процессе, отличном от процесса самого US-сервера. Исполнение в существующем пространстве процесса и постоянное расположение в памяти обеспечивает существенные преимущества с точки зрения как производительности, так и масштабируемости. Как и в CGI, одно ISAPI-приложение может выполнять разные задачи, принимая параметры, которые передаются в URL-agpeсе. Отличие ISAPI в том, что классы MFC скрывают от разработчика ISAPI-расширений многие из деталей разбора параметров. При использовании карт разбора (parse maps) (макрокоманд препроцессора, которые обычно применяются в MFC-приложениях) запросы «прозрачно» отображаются на функции-члены основного класса ISAPI-расширения, потомка класса CHnpServer. Для тех,


Основные сведения о разработке в ASP.NET

13

кому не нравится MFC, предусмотрена возможность использования в ISAPI-расширениях только классов, относящихся к ISAPI, в обход структуры классов в MFC. В облегченном серверном приложении это отсутствие дополнительного «багажа» может стать существенным преимуществом.

Недостатки ISAPI Почти все недостатки ISAPI относятся к процессу разработки ISAPI-приложений. Прежде всего, разрабатывать их сможет не всякий программист —для этого требуются знания C + + и MFC, а также HTML. Сказать, что перечисленные наборы навыков никак не связаны друг с другом, значит, не сказать ничего. Хотя большинство программистов знакомы с MFC и многие из них знают HTML, тех, кто знаком и с тем, и с другим, не так уж и много. Разработчики, знающие MFC, скорее всего разрабатывают традиционные Windows-приложения, для чего HTML не требуется. В отличие от других упомянутых в этой главе технологий разработки Интернет-приложений, разработку ISAPI-программ не так-то просто разделить на написание собственно приложения и создание интерфейсной части. ISAPI — это единая, монолитная DLL, и если не создавать собственные, доморощенные сценарии, дизайнеру интерфейса и разработчику прикладной бизнес-логики вряд ли удастся выполнить свои задачи изолированно друг от друга. Кроме поиска нужных специалистов необходимо предусмотреть решение еще одной проблемы — тестирования различных версий DLL-библиотеки. Создав простое приложение SayHellolSAPI, я сначала обратился по URL-адресу, но затем, перед открытием окна, показанного на рис. 1-3, вспомнил, что забыл расположить по центру текст в браузере, как в примере с CCI. Я перекомпилировал пример и попытался скопировать новую версию поверх старой в соответствующий каталог, но не тут-то было — я забыл о другом ограничении ISAPI: no умолчанию ISAPI-приложение загружается в память и остается там, пока работает Web-сервер. То есть, если не остановить сервер, заменить ISAPI-приложение не удастся. Можно потребовать, чтобы ISAPI-приложения не кэшировались на IIS, и обычно я так делаю на компьютере, на котором разрабатываю приложение. Однако, прежде чем выпустить ISAPI-расширение, вы должны протестировать приложение


Глава 1

14

при выключенном кэшировании, чтобы проверить, нет ли скрытых ошибок, связанных с: постоянно инициализированными переменными, ведь з предыдущем раунде тестирования DLL загружалась при каждом запросе. Помимо замены DLL-библиотеки на рабочем сервере, проблемы возникают при попытке отладки DLL. Разработчики, использующие MFC, — s частности и те, кто работают в среде Visual C + + , — вообще, привыкли к удобным средствам отладки при создании стандартных приложений в интегрированной среде разработки Visual С + +. Отладка iSAPl-приложения в среде Visual C++ задача решаемая, но решение это непросто.

Примечание Те программисты, которые специализируются на ASP.NET и которым нужна мощь и гибкость ISAPI-приложений и фильтров, могут воспользоваться HTTPhandlers и HTTPmodules.

Лучшее решение: ASP Вы можете спросить, зачем мы говорим об альтернативных решениях в книге, посвященной программированию ASP.NET; ответ заключается в деталях реализации ASP.NET и его предшественника ASP. Знакомство с ISAPI необходимо для более глубокого понимания ASP, а следовательно —и ASP.NET. Еще в бета-версии сервера I1S 2.0, который стал частью Windows NT 4.0, корпорация Microsoft представила новую технологию, которая в то время получила кодовое название «Denali». Тогда Microsoft активно использовала слово «Active» в названиях своих продуктов, и новую технологию переименовали в Active Server Pages, или ASP. Вышло несколько версий ASP, наиболее заметные из которых ASP 2.0 с IIS 4,0, включенная в пакет Windows NT 4.0 Option Pack (ASP 2.0 и IIS 4.0), и ASP 3.0 с IIS 5.0, введенная s Windows 2000. Я расскажу об ASP в целом, не останавливаясь на особенностях той или иной версии.


Основные сведения о разработке в ASP.NET

Как работает ASP Самые любознательные из вас спросят: а как ASP-сценарии преобразуются в HTML? Если коротко — с помощью ISAPI, Давайте-ка, я объясню поподробнее. Глубоко в недрах IIS есть показанное ниже диалоговое окно Application Configuration. Оно управляет тем, как обрабатываются файлы с различными расширениями, передаваемыми в URL-адресе. Обратите внимание, что для обработки URL-адреса с расширением .asp определена библиотека ASP.dll. Application rrnifiguratbon

asa hlr idc

shtm shtml

slm

nrinlei

D VwlMNT\System32SinetsrY\asp dll GET.HEAD... D:\WIN NT\Syslem32\inelsrv^asp.dll GETJHEAD. DAWINNTASjislen^inelsrAaspdll GET.HEAD,.. DAWINNT\System32\inelsrAisrr1.dl GET.POST DPTIONS.G GET.POST DAWINNT\Sj)steffl32\inelsiv\ssinc.dll GET.POST D:\WI NHTVSystem32\met5rvVHinc.dl GET.PDST

Cancel

I

Heip

На следующем снимке экрана показано окно средства Dependency Walker, поставляемого в составе Visual C+ + . В середине правой панели показаны функции, экспортируемые из ASP.dll. Интересно, что две из них — GetExterisionVersion и HttpExtensionProc — это функции, необходимые ISAPI-расширениям. Есть также необязательная функция TerminateExtension, которую способны поддерживать ISAPIрасширения.


к.

Глава 1

-. 'U MSVCRT.DLL Ш KERfJEL32.DLL E3 NTM.LOLI. i 23 U5ER32,DLt •Ш NTDLL.Dll •.JUEERNE13;.DL1

[NTDlL.Ca t «EHrtLJZ.DIL

~

OJDOMJ 3*0036)

S6 (ОПЗОЭв) 57 (0x0039)

DIIReglilerServer DllUnregisterSerus

Oi(003BJ OitOOSC) ОЯООЗО)

58 (0<ООЭА) 59 (ОхООЭВ) 60 (ОкООЗС)

GelEllenslonVBSl HtlpEitsnieriPioc TaimlruteExtHE,;

Возможно, вы ожидали, что ASP-страницы реализованы как ISAPI-фильтр, но, как видно из этого исследования библиотеки ASP.dll в составе IIS 5.0, они реализованы с применением ISAPI-расширения. Удивительно, как просто создать собственную систему, напоминающую ASP. Просто зарегистрируйте расширение, которое вы хотите обрабатывать, и добавьте его в диалоговое окно Application Configuration, a затем создайте DLL-библиотеку ISAPI, которая будет вызываться при указании файла с соответствующим расширением в URL-адресе. При запросе такого файла вызывается функция HttpExtensionProc из созданной библиотеки. DLLбиблиотеки ISAPI имеют доступ к функциям обратного вызова, что позволяет им получать всю необходимую для обработки запроса информацию. Но зачем может понадобиться создавать собственную DLLбиблиотеку ISAPI и сопоставлять ее определенному расширению файла? Это иногда необходимо при особых требованиях к сценарию, когда не удается найти других разумных альтернатив. Или когда нужно создать похожий на ASP механизм поддержки сценариев на пока еще неподдерживаемом языке. С появлением ASP.NET остается очень немного причин реализовывать другие языки таким способом ASP.NET предоставляет намного более удобный и мощный способ поддержки новых языков.


Основные сведения о разработке в ASP.NET

17

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

Примечание Активные серверные страницы (Active Server Pages), уже давно известные как А5Р, к сожалению, стали жертвой совпадения имен: в английском языке аббревиатура «ASP» также используется для обозначения поставщиков услуг приложений (Application Service Providers). Чтобы избежать путаницы, в этой книге ASP обозначает Active Server Pages, а поставщиков услуг приложений я буду называть поставщиками услуг приложений. Исходный ASP-текст обычно пишут на VBScript (Microsoft Visual Basic Scripting Edition), хотя можно использовать и Microsoft JScript. В листинге 1-3 показан пример ASP-приложения SayHelloASP, созданного на VBScript <Х Option Explicit X> <HTML> <HEAD> <TITLE>Hello ASP World</TITLE> </HEAD> <BODY> <CENTER> <%

Dim x For x=1 to 5

Response.Write("<FONT size=" & x) Response.Write(">Hello ASP World</FONT><BR>" & vbCrLf) Next

%> </CENTER> </BODY> </HTML> Листинг 1-3.

Приложение-пример SayHelloASP

Результат работы приложения SayHelloASP показан на рис. 1-4. 2 3 а к . 422


Глава 1

нрИо ASP World Ff

Htllo ASP -МдМ

Hello ASF World HeDo A:?P World

Hello ASP World

Hello ASP World

Рис. 1-4.

Результат работы приложения SayHelloASP

Приложение SayHelloASP чуть «круче» предыдущих примеров SayHelloCGI и SayHellolSAPI — я хотел продемонстрировать мощь ASP. Вместо простого одноразового отображения строки «Hello ASP World» приложение в цикле выводит строку с возрастающим размером шрифта. Первая строка листинга 1-3 — это директива Option Explicit компилятору VBScript. Она требует явного объявления всех переменных. (Подробнее об этой директиве и последствиях ее применения я расскажу далее, в разделе «Недостатки ASP».} Директива заключается между парами символов <% и %>. Они применяются в тексте ASP-страницы для обозначения сценариев. Сценарии, выполняемые на стороне клиенте, можно также выделять тэгами <SCRIPT> и </SCRIPT>. В приведенном примере, а первых шести строках, содержится стандартный исходный HTML-текст — как в обычном HTML-файле. Затем следует раздел с текстом сценария (обозначенный символами <%). Объявляется переменная с именем х, но обратите внимание, что тип не указан. В цикле For значение х увеличивается от 1 до 5, кроме то;ю, здесь используется метод Write объекта Response. Последний доступен во всех ASP-страницах, наряду с несколькими другими объектами, в числе которых Request, Server, Session и Application. Цикл завершает сценарий, конец которого обозначен символами %>, затем следуют несколько строк обычного HTML-текста. Цикл For можно записать и так:


Основные сведения о разработке в ASP.NET

19

а Dim x For x = 1 То 5 %> <FONT size=<X=x X»Hello ASP World</FONTXBH>

<% Next %>

Здесь для вывода пяти версий строки «Hello ASP World» не используется метод Response. Write. Вместо этого тэг шрифта и сам текст написаны напрямую, в одной строке особого вида <%=х%>. В HTML-тексте ASP-страницы присутствует группа символов <% = , после которой следует переменная и завершающий разделитель (%>), — это сокращенное обозначение инструкции использовать Response .Write для записи значения переменной в HTML-поток.

Примечание При отладке такого выражения, как <%=имя_переменной%>, необходимо учитывать некоторые особенности. Сообщение об ошибке, связанной, к примеру, с необъявленной переменной, ссылается на Response. И/г/?е('имя_переменнойЛ а не на фактическое выражение. Получив сообщение об ошибке в тексте, который отсутствует в вашем сценарии, вспомните, нет ли в программе соответствующих сокращений. Преимущества ASP Технология ASP мгновенно стала популярной, в значительной степени этому способствовала ее способность упрощать задачи, ранее казавшимися сложными (создание динамического Webсодержимого). Создавать CCI-и ISAPI-приложения не так уж трудно, но работа с ASP намного проще. По умолчанию в ASP используется VBScript. Можно без преувеличения сказать, что миллионы разработчиков по крайней мере немного знакомы с Visual Basic, Visual Basic for Applications (VBA) или VBScript. Для них ASP стал именно тем способом, который позволил войти в мир Интернета. Конечно же, они могли бы изучить новый язык программирования, но в ASP этого не требова-


20

Глава 1

лось. Отчасти из-за поддержки VBScript технология ASP стала жизнеспособным методом создания Web-приложений. Также важным фактором оказался относительно простой доступ к базам данных, осуществляемый посредством Microsoft ActiveX Data Objects (ADO). При создании динамического информационного наполнения соответствующую информацию нужно где-то брать, и технология ADO позволяла легко извлечь нужные данные. Наконец, и это, возможно, наиболее важный фактор, модель разработки в ASP позволила разработчикам эффективно писать и исполнять программы. Не нужно никакой компиляции или сложных операций по установке. Как вы узнаете из главы 4, создатели среды ASP.NET постарались сохранить эту модель разработки, даже при том, что внутренние механизмы существенно изменились.

Недостатки ASP ASP — мощный инструмент, предназначенный для Web-разработчиков, создающих крупные масштабируемые Web-приложения. ASP с большим успехом использовалась на Web-узлах, таких, как www.microsoft.com и www.deli.com, а также на множестве других больших и маленьких сайтов. V меня не было опыта построения крупных Web-узлов, тем не менее мне пришлось довольно много поработать с ASP при создании узла среднего размера — www.golfsodetyonline.com. Основной опыт работы с масштабируемостью реальных Интернет-приложений я приобрел в процессе разработки этого Web-узла, который, по моему мнению, является типичным сайтом среднего уровня. Первое, что меня заинтересовало при исследовании возможности применения ASP в интрасетях с одним сервером, —это возможные издержки интерпретации VBScript или JScript при каждом запросе, К моему большому удивлению, ASP-cpega обеспечивала достаточную скорость 'кроме нескольких заметных исключений). На большинстве ASP-сайтов среднего размера доступ к базе данных и обновления являются более критичными местами, чем «движок» ASP. В более поздних версиях ASP повысилась производительность предоставления Web-страниц, даже страниц с довольно сложными сценариями.


Основные сведения о разработке в ASP.NET

21

Почему так медленно выполняются операции со строками в VBScript Я очень много работал с С и С+ + и чуть меньше •— с Visual Basic, VBA и VBScript. Одна из моих самых больших претензий к Visual Basic вообще и к VBScript в частности — абсолютно неудовлетворительная производительность обработки строк. Вот бесхитростный пример на Visual Basic, в котором в конец строки добавляется 50 000 букв «А»: Private Sub GoSlow_Click() Dim tstr As String Dim tloop As Long For tloop = 1 To 50000 tstr = tstr & "A" Next MsgBox "Done" End Sub На моем компьютере с двумя процессорами Pentium с частотой 400 МГц на выполнение этой программы уходит около 12 секунд. Конечно, это крайность, но все равно не должно затрачиваться столько времени на добавление символов к строке, даже если их 50 000. Боб Снайдер (Bob Snyder), активно участвующий в сообществах Microsoft Access и Visual Basic, показал мне лучший способ достичь того же результата. Private Sub GoFast_CHck() Dim tstr As String Dim tloop As Long tstr = Space(SOOOO) For tloop = 1 To 50000 Hid(tstr, tloop, 1) = "A" Next MsgBox "Done" End Sub В этой программе на том же компьютере та же операция добавления в строку 50 000 символов выполняется мгновенно! Понятно, что проблема не в самой обработке строк, а в


22

Глава 1 выделении памяти для них. В программе GoSlow_Click при каждом из 50 000 вызовов команды tstr = tstr & «А» память под строку tstr выделяется повторно. Проблема в том, что в VBScript есть функция Mid, но нет оператора Mid, который можно было бы ставить слева от знака «равно» ( = }. В ASP.NET при подобных операциях со строками производительность почти такая же, но уже не удастся в качестве альтернативы воспользоваться классом StringBuilder, который обеспечивает лучшую производительность при работе с большим количеством строк.

В ASP нет гибкой, мощной и действительно масштабируемой среды программирования. Например, в листинге 1-3 при объявлении переменной к я не определил тип. Я и не мог этого сделать, потому что все переменные в VBScript одного типа — Variant. Переменные этого типа способны хранить любые данные, но тип данных не фиксируется. Например, я мог бы написать х="утка", а затем —х=7, и это совершенно верный код. Отсутствие переменных с точно определенными типами делает программы на VBScript уязвимыми ко всем ошибкам, которых нет в языках со строгим контролем типов. Вспомните, что в первой строке примера SayHelloASP в листинге 1 -3 располагается директива Option Explicit, Без нее в VBScript переменные создаются при первом их упоминании. Допустим, в вашей программе есть переменная х1, но вы ошиблись и написали х\ (в конце буква «I», а не цифра «1»), VBScript-среда спокойно создаст новую переменную х! без значения. Отсутствие необходимости объявлять переменные кажется удобным. В обзорах различных языков сценариев ASP-cpega заслужила дополнительные очки за отсутствие контроля типов, но это не соответствует требованиям профессиональных разработчиков, создающим надежные масштабируем.ые Web-узлы. Другое затруднение заключается в возможности совмещать стандартный HTML и текст сценариев. Точнее — в необходимости размещать команды сценария в HTML. Кроме снижения производительности из-за смены контекста при каждом входе и выхо-


Основные сведения о разработке в ASP.NET

23

ge из раздела сценария, смешанный код в «сыром» HTML чрезвычайно усложняет деление на представление и само приложение. Конкретный пример подобной проблемы — трудности, которые возникли, когда я работал над Web-узлом SportSoft Golf, создавая информационное наполнение для партнерских Web-узлов. Партнерство организовано по принципу, похожему на поставку услуг приложений. SportSoft Golf предоставляет информационное наполнение, а клиенты размещают на своих Web-узлах ссылки на узел SportSoft Golf. Фактическое местоположение информации не должно интересовать посетителей, и она предоставляется «прозрачно», где бы ни находилась — на сайте клиента или SportSoft Golf. Для выполнения этой задачи информационное наполнение, предоставляемое ассоциацией SportSoft Golf, должно выглядеть так же, как страницы Web-узлов клиентов. Для подобного трюка, в котором информационное наполнение не отличается от общего вида Web-узлов самых различных клиентов, требуется разделить представление и содержимое. В принципе, для этого можно применить средства ASP, но сделать это очень трудно. Одно из стандартных решений заключается в использовании сложной системы включения (include) файлов, которые обеспечивают раздельное «подключение» содержимого. Одного подключения файлов недостаточно, однако при использовании его совместно со сложным набором переменных, которые позволяют ввести в информационное наполнение особенности представления, такие, как цвета таблиц и другие, проблему удается решить. Поддержка множества подключаемых файлов и неструктурированная совместная поддержка особенностей представления в файлах информационного наполнения и представления —задача очень нетривиальная. Эта трудность, помноженная на реальные и мнимые слабости VBScript, привела к тому, что сложилось довольно прохладное отношение к ASP во многих областях разработки ПО, особенно среди программистов, использующих C/C+ + .


24

Глава 1

Новое решение: ASP.NET В момент совместного выпуска ASP версии 3.0 и Windows 2000 стало очевидным, что будущее разработки программ тесно связано с будущим Web. В качестве части инициативы .NET корпорация Microsoft предстазила ASP.NET — новую версию ASP, —где сохранена модель разработки, которая хорошо знакома и так полюбилась ASP-программистам: достаточно создать программу и поместить ее в нужный каталог с корректными разрешениями, и она будет работать. В ASP.NET представлены новшества, которые обеспечивают более простое деление на само приложение и собственно представление. В ASP.NET появилось много новых функций, а существовавшие в «классической» ASP значительно усовершенствованы. ASP.NET это не просто новая версия ASP, а действительно совсем новый продукт, хотя и предоставляет разработчикам туже методологию, которая им так понравилась в ASP. Вот перечень самых важных особенностей ASP.NET. • .NET Framework — архитектура, облегчающая проектирование традиционных и Web-приложений. (В главе 2 приведен краткий обзор .NET Framework.) • Единый язык среды исполнения (Common Language Runtime) обеспечивает единый набор сервисов для всех языков в ASP.NET. Если вам приходится комбинировать ASP-сценарии с СОМ-обьектами, вы оцените преимущества единого набора типов для всех языков. (Подробнее о едином языке среды исполнения — в глазе 2.) • Компилируемые языки. В ASP.NET обеспечивается повышенная производительность с помощью компилируемых языков. Они позволяют разработчику проверить по крайней мере синтаксическую корректность исходного текста. В ASP такая возможность отсутствует, поэтому там простые синтаксические ошибки обнаруживаются лишь при первом выполнении программы. (В главе 2 п расскажу о процессе компиляции и управляемом коде.) • Замечательные новые языки. Visual Basic .NET — это совсем новая версия Visual Basic с новым, более четким синтаксисом.


Основные сведения о разработке в ASP.NET

25

С# (произносится как «си-iuapn») — новый язык, очень похожий на C + + , но лишенный некоторых опасных особенностей, которые затрудняют создание надежных приложений на C+ + , Эти два языка доступны уже сейчас, но со временем появится поддержка других языков — об этом позаботятся сторонние поставщики. На момент написания этих строк появились реализации COBOL и Eiffel для Visual Studio .NET. (Visual Basic .NET и С# обсуждаются в главах 3 и 4.) • Visual Studio .NET — новая замечательная среда разработки, которая поддерживает быструю разработку (Rapid Application Development, RAD) серверных приложений. (Подробнее о Visual Studio .NET — в главе 4.) • Улучшенные компоненты. Каркас .NET Framework поддерживает использование новых типов компонентов, которые очень просто заменять в работающих приложениях. (О создании компонентов для ASP.NET рассказывается в главах 6 и 7.) • Web-формы позволяют программировать в стиле Visual Basic с использованием обработчиков событий в стандартных элементах управления HTML. (Web-формы обсуждаются в главе 5.) • Web-сервисы в поддержкой XML позволяют разработчикам создавать сервисы и предоставлять доступ к ним по протоколам, являющимся отраслевым стандартом, {Подробнее о Webсервисах — в главе 10.) • ADO.NET. ADO в .NET Framework — новая версия этой технологии, которая упрощает доступ из ASP.NET-приложении к данным реляционных БД и данных в других форматах, например XML. (XML и ADO.NET обсуждаются в главах 8 и 9.)

Заключение Эта краткая история разработки Web-приложений позволит вам лучше разобраться в особенностях ASP.NET. Изучение языка программирования или среды разработки очень напоминает изучение живого иностранного языка. Хотя словари и книги по синтаксису очень полезны, но зачастую требуется знание, хотя бы минимальное, истории страны и народа.


26

Глава 1

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


Глава 2

Runtime Как вы помните, в глазе 1 я говорил, что для ASP-разработчиков доступны два языка сценариев —VBScript (Visual Basic Scripting Edition) и JScript. ASP-страницы размещают в каталогах US-сервера (Internet Information Services), настраивают должным образом разрешения, и когда клиент запрашивает страницу, текст сценария на странице интерпретируется и клиенту возвращается готовая HTML-страница. Подобная модель понятна даже людям, не знакомым с программированием. В отличие от ISAPI (Internet Server Application Programming Interface) или компонентов COM-I-, текст ASP-страницы можно при необходимости изменять. Клиенты немедленно видят результаты изменений (если, конечно, не учитывать кэширование страниц на стороне клиента). Одно из наиболее значительных улучшений в ASP.NET заключается в способе работы с исходным текстом в среде исполнения (runtime). Как я говорил в главе 1, модели разработки в ASP.NET и ASP похожи, но скрытые механизмы этих технологий сильно отличаются. В ASP.NET вместо интерпретации исходной страницы при каждом обращении клиента страница компилируется, преобразуясь в формат промежуточного языка MSIL (Microsoft Intermediate Language) лишь при первом обращении. После компиляции страницы в MSIL JiT-компилятор Oust-In-Time compiler) преобразует MSIL в машинный код.


28

Глава 2

Примечание Можно не ожидать первого запроса страниц ASP.NET, а сразу принудительно скомпилировать все страницы в формат MSIL — это позволит выявить синтаксические и другие ошибки. Самый простой способ компилировать все файлы «за раз» -- создавать приложение в среде Microsoft Visual Studio .NET. В этой главе я сначала расскажу о каркасе .NET Framework, а затем более детально познакомлю вас с языком MSIL и JIT-компилятором. Далее я объясню, как благодаря JIT-компилятору в ASP. NET становится доступной та же модель разработки, что и G ASP. Затем вы познакомитесь с управляемым кодом и данными, в том числе с некоторыми преимуществами и недостатками использования управляемого кода. В заключение я расскажу о небезопасном коде, который выполняется вне среды исполнения CLR.

Краткий обзор „NET Framework Каркас .NET Framework разработан «с нуля»; его задача — предоставить программистам более эффективную и гибкую среду разработки как традиционных, так и Web-приложений. Одна из наиболее важных особенностей .NET Framework — его способность обеспечить «гладкую» совместную работу кода, написанного на различных языках. На рис. 2-1 показана структура .NET Framework на самом высоком уровне. В основе всего каркаса лежат системные сервисы. В текущей реализации это ядро состоит из сервисов Win32 API и COM4-, хотя теоретически уровень абстрагирования можно создавать на основе сервисов любой операционной системы. Традиционно приложения вызывают API-функции операционной системы напрямую. В мире Win32 эту модель весьма трудно реализовать разработчикам, программирующим на Visual Basic, так как некоторые API-функции требуют использовать структуры данных, которые легко создаются в C/C++, но отсутствуют в Visual Basic.


Управляемый код и среда Common Language Runtime Visual Basic .NET

JScript.NET Common language specification (CLS)

ASP.NET Web-формы

|

Windowsформы

XML Web-сервисы

Visual Studio -NET ADO.NETMXML

Классы .NET Framework

Среда исполнения Common Language Runtime

Windows

Серзисы CQM+

Рис. 2-1. Архитектура .NET Framework Уровнем выше системных сервисов располагается среда исполнения Common Language Runtime (CLR). Она загружает и выполняет код на любом предназначенном для этой среды языке. Такой код называется управляемым (managed code). (Подробнее о нем я расскажу чуть попозже в этой главе.} Среда исполнения CLR также поддерживает встроенную всеобъемлющую систему безопасности. В предыдущих Win32-cpegax в лучшем случае обеспечивалась безопасность файловых систем и сетевых ресурсов. Например, в Microsoft Windows NT и Microsoft Windows 2000 защита файлов доступна лишь на томах NTFS. Среда исполнения CLR обеспечивает безопасность доступа к коду, что позволяет разработчикам определять разрешения, необходимые для исполнения программы. Во время загрузки и при выполнении методов среда исполнения способна определять, разрешено ли предоставлять коду требуемый доступ. Разработчики могут также явно назначать ограниченные разрешения, например коду, выполняющему простые и не очень опасные действия, назначать минималь-


30

Глава 2

ные разрешения. Это коренным образом отличается от нынешней ситуации, когда VBScript-сценарии беспрепятственно исполняются в почтовых клиентах (например, Microsoft Outlook) и зачастую наносят непоправимый вред. Такую возможность активно используют создатели вирусов. Даже в защищенной системе, если пользователь с правами администратора неосторожно запустит зараженный вирусом VBScript-сценарий, последнему становятся доступными все действия, разрешенные администратору. Основанная на ролях система безопасности, которую поддерживает среда исполнения CLR, позволяет устанавливать разрешения, предоставленные пользователю, в чьем контексте выполняется код. Среда исполнения CLR поддерживает работу классов .NET Framework, которые можно вызывать из любого языка программирования, поддерживающего .NET. Имена классам присваиваются по логичным правилам конструирования, что облегчает и ускоряет их изучение разработчиками. О библиотеках классов я расскажу в главе 3; они распространяются на практически все мыслимые области — от сервисов доступа к данным до организации потоков и поддержки сети. Поверх библиотеки классов .NET Framework располагаются ADO.NET и XML-данные. ADO.NET — это набор классов, применяемых в .NET Framework для доступа к данным. ADO.NET базируется на спецификации ADO, но поддерживает XML и работу в неоднородной среде. Уровнем выше ADO.NET и XML находится слой поддержки двух различных типов приложений. Первый —это традиционные клиентские прикладные программы, в которых используются формы Windows [например, создаваемые в Visual Basic с использованием библиотеки фундаментальных классов Microsoft (MFC)]. Другой тип — приложения ASP.NET, в том числе Web-формы и XML Web-сервисы. Выше ASP.NET и форм Windows располагается слой спецификации общего языка (Common Language Specification, CLS) и языков, которые соответствуют ей. CLS — это набор правил, которым должен соответствовать CLS-совместимый язык. Таким образом гарантируется, что у языков есть обязательный набор общих характеристик.


Управляемый код и среда Common Language Runtime

31

Промежуточный язык Microsoft Intermediate Language Хотя описание работы ASP.NET и .NET Framework часто напоминает рассказ о работе виртуальной машины Java (Java Virtual Machine, JVM), ASP.NET и JVM отличаются. Компилятор Java создает байт-код, который выполняется во время исполнения JVM-машиной. Этот способ немного отличается от принятого в .NET Framework метода, который заключается в создании машинного кода на основании текста на промежуточном языке во время исполнения, но это небольшое различие влечет значительные последствия в плане производительности. Использование байт-кода в Java действительно ничего нового не представляет. В прошлом такой подход пытались применить в других средах, и обычно это заканчивалось неудачей — частично из-за недостаточной мощности тогдашних аппаратных средств и отчасти по причине отсутствия в то время Интернета. Что коренным образом отличает .NET Framework, так это код, который не интерпретируется во время исполнения, а преобразуется в «родной» машинный код и выполняется напрямую. Одно из преимуществ Java (которое время от времени раздражает разработчиков) — жесткая система безопасности, поддерживаемая в модели Java/JVM. В .NET Framework обеспечивается такой же уровень безопасности наряду с возможностью исполнять «родной» код — при условии, конечно, что у пользователя достаточно прав. Существенное преимущество .NET Framework перед Java и JVM выбор языка программирования. Если вы пользуетесь JVM, у вас одна возможность — Java. Несомненно, Java прекрасный язык программирования, но это лишь один язык. Разработчикам, которые привержены Visual Basic или C++, придется потратить время на изучение модели Java/JVM, a .NET Framework позволяет программистам пользоваться любым языком, который им знаком, от Visual Basic и С# до Eiffel и COBOL. Давайте-ка взглянем на простейшую программу на Visual Basic .NET: Public Module modmain Sub Main() System.Console.WriteLineC'Hello .NET World!")


32

Глава 2

End Sub

End Module

Сейчас не обращайте внимание на особенности, отсутствующие в более ранних версиях Visual Basic. Задача программы понятна просто вывести в консоль строку «Hello .NET World!». Детали пока не важны — нас интересует вывод. Чтобы скомпилировать эту программу на машине с .NET Framework, необходимо выполнить следующую команду: Vbc HelloDotNet.vb /out;HelloDotNet.exe

Примечание В ASP.NET не обязательно знать, как пользоваться компилятором командной строки, особенно если вы планируете работать в Visual Studio .NET. Однако эти знания позволяют лучше разбираться, что происходит в Visual Studio .NET, а также автоматизировать выполнение задач. В результате получим исполняемый файл размером около 3 кбайт, который, как я уже говорил, действительно отображает в консоли фразу «Hello .NET World!». Исполняемый файл состоит из двух частей: первая — это MSIL-код, который используется для создания машинного кода, а вторая — это метаданные, то есть информация о коде и других элементах, которая требуется среде исполнения CLR. В составе .NET Framework поставляется программадизассемблер MSIL Disassembler (lldasm.exe). Если выполнить ее, передав в качестве аргумента имя только что созданного файла, вы получите информацию, похожую на показанную на рис. 2-2.

> MANIFEST » .class pubic Шо 4nsi saalad > cuilomintltnce void [MicrotekVisualBaiiclMienuiittVinuBs 3 Mam: vcid(]

i

Рис. 2-2. Окно утилиты lldasm.exe при анализе приложения HelloDotNet.exe


Управляемый код и среда Common Language Runtime

33

Для нас важен последний элемент в дереве: Main : void(). Специалисты по C/C + + узнают ссылку на void, которая указывает, что это отрезок кода, который не возвращает никакого значения. Функция в C/C ++, возвращающая void, полностью аналогична функции Sub в Visual Basic. Двойной щелчок рассматриваемого элемента открывает окно, содержащее следующий текст: .method public static void

Hain() ciL managed

.entrypoint .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) // Code size 11 (Oxb) .maxstack В ILJJOOO: ldstr"Hello .NET World!" IL_0005: callvoid [mscorlib]System.Console::WriteLine(string) IL.OOOa: ret } // end of method modmain::Main Даже на зная исходного текста этой простой программы и не владея MSIL, не слишком трудно догадаться, что происходит на самом деле. Строка И_0000 загружает строку-константу «Hello .NET World!». Следующая строка вызывает другую функцию void —System.Console;:WriteLine, которая ожидает на входе строку. Обратите внимание также на ссылку на mscorlib — пока вам ничего не остается, как просто поверить мне, что это основная библиотека в .NET Framework. Если нажать сочетание клавиш Ctrl+M откроется окно с метаданными программы HelloDotNet.exe (листинг 2-1). ScopeName : HelloDotNet.exe MVID : {D9382B73-AF72-4778-8164-38EEA6400342} Global functions

Global fields Global MemberRefs TypeOef #1


34

Глава 2

TypDefName: modmain (02000002) Flags: [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] (00000101) Extends : 01000001 ["ypeRef] System.Object Method #1 [ENTRYPOINT] MethodName Main (06000001) Flags [Public] [Static] [ReuseSlot] (00000016) RVA 0x00002050 ImplFlags [IL] [Managed] (00000000) CallCnvntn [DEFAULT] ReturnType Void No arguments. CustomAttribute «1 (OcOOOOOt) CustomAttribute Type: ОаОООООЗ CustomAttributeName: System.STAThreadAttribute instance void .ctor() Length: 4 Value : 01 00 00 00 > < ctor args: () CustomAttribute »1 (Oc000002) CustomAttribute Type: Oa000002 CustomAttributeName: Microsoft. VisualBasic.CompilerServices.StandardModuleAttribute :: instance void ,ctor() Length: 4 Value : 01 00 00 00 > < ctor args: () TypeRef #1 (01000001) Token: 0x01000001 ResolutionScope: 0x23000001 TypeRefName: System.Object Typeflef «2 (01000002) Token: 0x01000002 ResolutionScope: 0x23000001 TypeRefName: System.Console MemberRef #1


Управляемый код и среда Common Language Runtime

35

Member: (OaOOOOOl) Writeline: CallCnvntn: [DEFAULT] ReturnType: Void 1 Arguments Argument #1: String TypeRef #3 (01000003) Token: 0x01000003 ResolutionScope: 0x23000002 TypeRefName:

Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute MemberRef tf1 Member: (Oa000002) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments.

TypeRef #4 (01000004) Token: 0x01000004 ResolutionScope: 0x23000001 TypeRefName: System.STAThreadAttribute MemberRef *1 Member: (ОаОООООЗ) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. Assembly Token: 0x20000001 Name : HelloDotNet Public Key : Hash Algorithm : 0x00008004 Major Version: 0x00000000 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> Flags : [SideBySideCompatible] (00000000) AssemblyRef #1


36

Глава 2

Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 eO 89 Name: mscorlib Major Version: 0x00000001 Minor Version: 0x00000000 Build Number: OxOOOOOcle Revision Number: 0x00000000 Locale: <null> HashValue Blob: Flags: [none] (00000000) AssemblyRef tf2 Token: 0x23000002 Public Key or Token: bO 3f 5f 7f 11 d5 Oa За

Name: Microsoft.Visual Basic

Major Version: 0x00000007 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> HashValue Blob: Flags: [none] (00000000) User

Strings

70000001 : (17) L"Hello .NET W o r l d ! "

Листинг 2-1. Метаданные программы HelIoDotNet.exe, полученные средствами утилиты lldasm.exe Первое, что бросается в глаза, — метаданных очень много. Они организованы в таблицы, в которых, по сути, и описано, что определяет и на что ссылается ваш код. Например, TypeDef #1 — это таблица определений, содержащая информацию о процедуре Main, которая реализована в коде. В таблице видно, что Main не возвращает никаких значений (ReturnType: Void] и не принимает никаких аргументов (No arguments]. TypeRef #2 — это таблица ссылок; она содержит информацию о классе System.Console каркаса .NET Framework, на который содержится ссылка в коде. Таблица TypeDef #2 также ссылается на метод WriteLine, который не возвращает значений., но принимает один аргумент строкового типа String. Метаданные могут также содержать сведения об


Управляемый код и среда Common Language Runtime имени и версии, файлы и сборки, на которые есть ссылки, разрешения безопасности и другую информацию. Наверняка вы спросите: зачем нужны все эти метаданные? Вопервых, они не зависят от языка описания кода, а во-вторых, ваша сборка сама себя описывает и позволяет другим средам определять поддерживаемые ею функции. Сборка (assembly) это один или большее число логически связанных файлов, которые развертываются совместно. По сути, HelloDotNet.exe — это сборка, состоящая из одного файла. Подробнее о сборках я расскажу в главе 6. При проектировании Web-сервисов метаданные обычно применяются для создания файлов-описаний сервисов в формате WSDL (Web services Description Language — язык описания Webсервисов). Подробнее о Web-сервисах рассказано в главе 10, но если коротко, то Web-сервис —это программный компонент или сервис, доступ к которому осуществляется через Web. Давайтека немного забежим вперед: посмотрите на рис. 2-3, где показана Web-страница, автоматически сгенерированная при обращении к странице ASP.NET, созданной как Web-сервис.

MathService

Рис. 2-3. Web-страница суммирования двух значений, автоматически сгенерированная Web-сервисом Как видите, Web-сервис содержит метод Add, который, как и следовало ожидать, принимает два аргумента. Введите число «2»


38

Глава 2

в обоих полях Value и щелкните кнопку Invoke. Результат возвращается в виде набора в формате XML (рис. 2-4). Более детально с XML и доступом к данным вы познакомитесь в глазе 8.

-:' • ••• • /''•: _i . г ; •:

oat ^cr::ns="htlp://tenipijri,tir(!/">4i/float;

Рис. 2-4.

Результат вызова метода Add на рис. 2-3

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

Примечание Вообще говоря, возможность «видеть» детали кода, основанного на .NET Framework, является большим преимуществом. Однако с этим могут не согласиться разработчики, которые создают двоичные программы для выполнения на рабочих станциях клиентов, а не Web-страницы или Webсервисы. На момент написания этих строк не существует признанного способа скрыть эту информацию, хотя в теории ничто не может запретить скрывать подобные данные. Например, можно дать методу GetSecretCode («получить секретный код») запутанное название, допустим DDFeewsayppfgeEk, чтобы сделать его менее заметным без ущерба для работы среды исполнения, осуществляющей проверку, связанную с защитой. Подобное «запутывание» используется для сокрытия кода клиентских сценариев Jscript, а также исходных текстов на С, которые предоставляются клиентам. К счастью, разработчикам, создающим приложения ASP.NET, не придется прибегать к подобным ухищрениям.


Управляемый код и среда Common Language Runtime

39

Компиляция «Just in Time» Теоретически, как и в Java, код на языке MSIL можно скомпилировать и исполнять на любой платформе, которая поддерживает среду исполнения CLR. На сегодняшний день такая среда существует лишь в архитектуре Intel с ОС Windows, но вполне вероятно, что она появится и на других платформах. Такая уверенность основывается на наличии JIT-компилятора Oust in Time). На рис. 2-5 показаны процессы компиляции и исполнения. КОМПИЛЯЦИЯ

Исполнение Рис. 2-5. Компиляция и исполнение управляемого кода Можно подумать, что компиляция такого ассемблерного кода, как MSIL, должна сказываться на производительности приложения, На самом деле дополнительные издержки очень малы и практически незаметны. Отчасти это, конечно же, объясняется умением разработчиков JIT-компилятора, но в основном такое поведение — результат механизма, по которому работают программы. Вообще говоря, при работе программы исполняется далеко не каждая ее строка. Например, код обработки ошибок выполняется лишь в исключительных случаях. Чтобы воспользоваться этим фактом и не компилировать с самого начала весь MSIL-код в машинный, jIT-компилятор компилирует код лишь по мере необходимости, а откомпилированный код кэшируется для повторного использования. MexaнuзмJIT-тpaнcляцuu довольно прямолинеен. При загрузке класса загрузчик «закрывает» заглушками все методы класса. Когда метод вызывается в первый раз, код заглушки передает управление JIT-компилятору, который преобразует MSIL-текст в машинный код. Затем заглушка заменяется на указатель на созданный машинный код, и последующие запросы выполняются непосредственно этим кодом.


40

Глава 2

Управляемый код и данные Так что же представляет собой управляемый код? Управляемый код (managed code) — это тот, который предоставляет среде исполнения CLR достаточно информации для выполнения следующих задач: • поиска метаданных, описывающих метод, по адресу в коде; • прохода стека; • обработки исключений; • хранения и поиска информации, связанной с безопасностью. Чтобы выполнение всех этих задач стало возможным, код должен предварительно пройти специальную проверку, или верификацию (verification process), если только политикой не разрешено его исполнение без проверки. В процессе верификации JIT-компилятор исследует MSIL-текст и метаданные, пытаясь выяснить, поддерживается ли контроль типов. Код с поддержкой типов обращается только к областям памяти, которые ему принадлежат, — это гарантирует его корректную работу и правильное взаимодействие с другими программами, а также то, что он не станет причиной случайного или злонамеренного разрушения системы. В отсутствие контроля типов нельзя четко соблюсти правила безопасности. С управляемым кодом связаны управляемые данные (managed data) —данные, память для которых выделяется и освобождается автоматически средой CLR с применением механизма, который называется «сборка мусора» (garbage collection). Кроме прочего, последний отвечает за освобождение памяти, выделенной объектам, которые вышли из области видимости. Одно из последствий использования «сборки мусора» заключается в том, что в принципе нельзя определить время и даже порядок разрушения объектов. Посмотрите на следующий исходный текст на С#. (О С# я расскажу в главе 3, но для понимания этого фрагмента особые знания не требуются.) class MainApp { public static void Maii() {

System. String Hello •= "Hello"; System.String World « "World!";


Управляемый код и среда Common Language Runtime

i

41

System.Console.WriteLine(Hello); System. Console. Writel_ine( Wo rid); I

Здесь создаются два объекта строкового типа String: один содержит строку «Hello» и второй — «World!». Хотя строки созданы именно в этом порядке, ничего нельзя сказать о порядке, в котором они будут уничтожены. Более того, мы даже не можем сказать, будут ли они немедленно разрушены при выходе из области видимости. В этом примере порядок или время уничтожения не имеют значения, но в других случаях это бывает существенно. «Свободное», не поддающееся прогнозу освобождение ресурсов, занятых объектами, не вызывает проблем, пока объект не связан с постоянными ресурсами, которыми среда исполнения CLR управлять не в состоянии, например подключениями к базе данных или описателями окон. В этом случае необходимо создать метод Dispose и реализовать интерфейс /Disposable, которые следует явно вызывать для освобождения ресурсов. Примеры применения этой методики приводятся в последующих главах.

О небезопасном коде Существуют ситуации, з которых не удается воспользоваться управляемым кодом. Например, для многих встроенных Win32функций требуются указатели. С отсутствием указателей в Visual Basic сталкиваются программисты, пишущие на C/C++. В Visual Basic .NET указателей также нет, но здесь поддерживается аналогичный механизм, он называется ссылочный тип (reference type). Я считаю, что в некоторых случаях указатели все же более удобны. Иногда требуется использовать неуправляемый унаследованный код. Управляемый код предоставляет массу преимуществ, и наверняка нам бы хотелось, чтобы все имеющиеся программы волшебным образом превратились в управляемый код, но иногда это просто невозможно. Так как же поступать, когда без указателя или унаследованного кода никак не обойтись? Для таких случаев в С# предусмотрено специальное ключевое слово — unsafe («небезопасный»). Метод или отрезок программы, объявленный небезопасным и скомпилированный с ключом /unsafe, преобразуется в небезопасный (не управляемый) код, ко-


42

Глава 2

торый не подвергается процедуре верификации в среде исполнения. Кроме unsafe в С# есть ключевое слово fixed. В процессе «сборки мусора» переменные часто перемещаются — это позволяет более эффективно использовать память. Например, когда требуется выделить память для большого объекта, а свободная память доступна в виде нескольких мелких блоков, механизм «сборки мусора» часто предварительно перемещает блоки, освобождая область побольше. Понятно, что такая реорганизация становится катастрофой для программы с небезопасным кодом, где используется указатель на одну из перемещенных «сборщиком мусора» переменных. Ключевое слово fixed создано именно для таких ситуаций. В блоке, обозначенном словом fixed, все переменные закрепляются, и их запрещено перемещать. После выхода из блока fixed переменные снова поступают s распоряжение механизма «сбора мусора».

Заключение ASP-программистам редко приходится разбираться в особенностях API-интерфейса Win32, лежащего в основе ASP на US-сервере. В самом деле VBScript и JScript предоставляли очень ограниченные возможности выполнения того, что выходило за пределы «компетенции» языка. С другой стороны, программистам, работающим в среде ASP.NET, предоставляется полный доступ ко всем богатым возможностям .NET Framework. Языки С# и Visual Basic .NET совместно с каркасом .NET Framework позволяют разработчикам делать практически все, что доступно программистам, пишущим программы с использованием Win32.


Глава 3

В настоящее время при разработке реальных систем приходится решать две важные проблемы — обеспечение работы создаваемой программы на нескольких платформах и взаимодействие частей приложения, написанных на разных языках. Как вы узнаете из этой главы,.NET Framework предлагает элегантные решения обеих проблем. Но сначала — небольшой экскурс в историю, Попытка решения проблемы многоплатформенных приложений была предпринята при создании языка программирования Java корпорацией Sun Microsystems. Для выполнения Java-программ на компьютере устанавливается виртуальная машина Java (Java Virtual Machine, JVM). Во время исполнения программы она интерпретирует байт-код на Java. Поскольку такие виртуальные машины распространяются з составе браузеров для различных платформ, иногда кажется, что Java позволила частично решить проблему. Но в действительности часто наблюдается несовместимость даже при исполнении байт-кода на одной платформе. Недавно в одной Java-программе я применял переключатели без надписей. Для этого в качестве текста надписи я задал пустую строку. В общем случае это позволило решить задачу, но при выборе такого переключателя в Microsoft Internet Explorer на месте текста появлялся небольшой пунктирный прямоугольник. Казалось, неполадку устранить просто: вместо пустой строки явно присвоить свойству «текст» переключателя значение null. Это работало до поры до времени, но с выходом новой версии Netscape Navigator присвоение null не только перестало работать, но приводило к аварийному завершению работы браузера. При этом сообщалось


44

Глава 3

об ошибке в исходном коде на С + +. Такова межплатформенная совместимость в Java. На заре эры персональных компьютеров проблема межплатформенной совместимости стояла еще острее. Для всего этого разнообразия различных моделей ПК и платформ в значительной степени требовалась единая среда разработки. Кое-какие новшества позволили свести эту проблему к минимуму. Прежде всего, ассемблерный код Intel x86 стал ближе к универсальному языку ассемблера. Практически во всех нынешних приложениях предусматривается исполнение на Intel-совместимом компьютере. И даже на других аппаратных платформах, в особенности Apple Macintosh, разрабатывается среда эмуляции для исполнения Intel-совместимых программ. Вторым важным изменением, повлиявшим на решение проблемы межплатформенной совместимости, стало бурное развитие Интернета. Интернет предоставляет единую платформу, позволяющую приложениям на различных платформах взаимодействовать друг с другом, и новейшее оборудование, такое, как беспроводные устройства, не исключение. HTML и JavaScript обеспечивают на стороне клиента достаточно функциональную рабочую среду. Конечно, в результате Интернет-бума возросли требования к перекрестной совместимости программ на различных платформах — это особенно верно в отношении создания сложного пользовательского интерфейса на стороне клиента; эту нишу заполнил Java. Как я уже говорил в начале главы, вторая проблема, с которой сталкиваются разработчики программного обеспечения, — взаимодействие частей приложения, написанных на разных языках. В настоящее время на доминирующей платформе (Microsoft Windows на Intel-coiBMecTUMbix системах) доступно большинство технологий и языков программирования. Наиболее популярные — Microsoft Visual Basic, C/C + + и Borland Delphi. Менее распространены, но активно применяются COBOL, Fortran и PERL. С первый дней Windows существовала возможность вызова динамически подключаемых библиотек (dynamic-link library, DLL) практически из любой среды разработки, но это не всегда давалось легко. Например, простая передача строки в качестве параметра, иногда вызывала значительные затруднения. В боль-


Объекты и языки .NET Framework

45

шинстзе языков программирования перед передачей строки следует позаботиться о выделении достаточного места в памяти. В некоторых средах разработки программисты этого не делают, Например, Visual Basic берет управление строками на себя, и при передаче строки в другую функцию по ссылке можно добавлять в нее сколько угодно информации, не заботясь о выделении пространства. Еще хуже обстоят дела с пользовательскими типами данных. Один недавний пример: способ, с помощью которого Visual Basic заполнял переменные-члены таких типов, вызывал хаос з программах. В последние годы связующим звеном между компонентами, написанными на различных языках, стала технология СОМ. Она решает проблему унификации типов данных, но никак не решает проблем с программированием Win32 API. Для применения Win32 API в Visual Basic требуется создавать очень нехарактерные для этого языка структуры данных. Сложности также часто возникают при программировании на уровне Win32 API в других языках. Строковый тип BSTR, поддерживаемый СОМ, не оченьто рассчитан на С и C+ + . Эти проблемы решены в .NET Framework. Во-первых, в нем представлена система типов данных, которые легко маршализуются между языками .NET без потери точности. В .NET Framework разработчикам больше не придется беспокоиться о том, на каком языке написана программа, использующая создаваемый ими класс или компонент. Они смогут больше времени уделять решению основной задачи и меньше тратить на оценку того, каким образом созданный на C++ клиент их серверной программы будет преобразовывать строку или денежный тип данных. Во-вторых, .NET Framework предоставляет виртуальную среду исполнения, которая решает задачу переносимости без ущерба для производительности. Приложения, созданные на платформе .NET, прекрасно работают на любой платформе. О том, как это происходит, я расскажу чуть позже.

Решение проблемы совместимости типов в.NET Отличительной чертой любой большой программной среды является хорошо продуманная объектная модель. Очень трудно


46

Глава 3

исправлять ошибки в нагромождении плохо спроектированных объектов и создавать при этом программы мирового класса. Хорошую объектную модель легко расширить собственным кодом, Основой объектной модели в .NET Framework является поддерживаемая на уровне каркаса система типов. Сейчас, как мне кажется, самое время уточнить некоторые термины. Говоря о типе переменной, я имею в виду то, что в ней предполагается хранить. Например, нельзя присвоить переменной целого типа значение «dog» или «Fred», а в переменной с типом «дата» значение 7/24/1956 вполне разумно, а 7 — нет. Программистам, работающим с классической технологией ASP (Active Server Pages), различные типы переменных довольно непривычны, так как в языках этой технологи все переменные одного типа — Variant. Другими словами, в одной строке программы переменная может иметь значение 7, а на следующей «Fred». Многим начинающим программистам единственный тип данных кажется удобным, однако их более опытные коллеги знают, что такая «вольница» приводит к путанице. Несмотря на то, что явное преобразование переменных между типами требует определенных усилий, оно позволяет гарантировать корректность преобразования. На рис. 3-1 показана взаимосвязь между различными типами, поддерживаемыми .NET Framework. Одни из них, вероятно, вам уже знакомы, происхождение других требует понимания некоторых сравнительно новых понятий, таких, как упаковка и сопоставление размерных типов и ссылочных типов. Я объясню новые понятия, относящиеся к типам .NET Framework.


Объекты и языки .NET Framework

47

Самоолйсываемые типы Пользовательские размерные типы

Перечисления

Типы-классы

Пользовательские классы

Указатели

Типыинтерфейсы

Массивы

Упаковочные ТИПЫ

Делегаты

Рис. 3-1. Система типов .NET Framework

Размерные типы Размерные типы (value type) — это сравнительно небольшие по объему данные, которые представимы в виде последовательности битов. Например, в языках С/С+ + и Visual Basic 6.0 есть типы int и long — они применяются для представления чисел и обычно используются в большинстве вычислений в любой программе.

Примечание Один из изъянов системы типов в языках программирования среды Visual Studio 6.0 ее недостаточная логичность и согласованность. Например, представьте мое удивление, когда я обнаружил, что на прекрасно работавшей до этого ASP-странице произошел очень серьезный сбой. Сообщение об ошибке указывало на переполнение переменной. После проверки причина ошибка стала очевидной -- значение поля идентификатора пользователя в системе превысило верхний предел целого числа в Visual Basic, и вызов функции Cint привел к аварии. Такому опытному программисту, работающему с C/C + +, как я, не составило труда разобраться в случившемся. В мире Win32-uHTep-


Глава 3

411

фейса nog тип int в лзыках C/C + + отводится 4 байта, а в Visual Basic: 6.0 - - только 2. Единый набор типов в .NET Framework позволит снизить вероятность возникновения подобной ситуации. В таблице 3-1 перечислены некоторые встроенные размерные типы в .NET Framework и указана их совместимость со спецификацией общего языка (Common Language Specification, CLS). Таблица 3-1.

Некоторые размерные типы B.NET Framework

Имя класса

Совместимость

Описание

с CLS 5y5tem.fi/fe System. SByte System. Intl 6 System. Int3 2 System. Int64 System. Ulntl 6 System.Uint32 System. Ulnt64 System. Single

Да Нет Да Да Да Нет Нет

System. Double

Да

System. Boolean System.Char

Да Да

System.Decimal System. In tPtr

Да Да

System. U'IntPtr

Нет

System.Object System.String

Да Да

Нет

Лл

8-битное целое без знака 8-битное целое со знаком 16-битное целое со знаком 32-битное целое со знаком 64-битное целое со знаком 16-битное целое без знака 32-битное целое без знака 64-битное целое без знака 32-битное число с плавающей точкой 64-битное число с плавающей точкой Значение true или false 16-битный символ в формате Unicode 96-битное десятичное число Целое со знаком, зависит от платформы Целое без знака, зависит от платформы Корневой объект Строка фиксированной длины, состоящая из символов в формате Unicode


Объекты и языки .NET Framework

49

Примечание В Visual Basic .NET недопустимо использовать тип System.Sbyte, а также любой целый тип без знака (System.Ulntl6, System.Ufnt32 или System.Ulnt64). Ссылка на эти типы данных вызывает ошибку. В С# подобных ограничений нет, но для максимальной межъязыковой совместимости рекомендуется не применять указанные типы, кроме тех случаев, когда без них ну никак не обойтись. В Visual Basic .NET меньше проблем с беззнаковыми типами данных из-за большего числа знакомест, выделяемых под переменные типа System.Int64. Все типы, заканчивающиеся на 16, соответствуют значениям типов размера WORD в Win32, на 32 — DWORD и на 64 — QWORD. В действительности при работе с языками, поддерживаемыми .NET, осе значения типов System не используются. Например, вС# есть собственный тип \nt, который соответствует System.Int32. Конечно, вы вправе вместо int использовать System.Int32, но это неразумно, так как исходный текст сложнее читать. Использование полных имен типов данных System оправдано лишь в особых ситуациях. Если вы создаете компоненты для коммерческого распространения, лучше явно указывать используемые типы, потому что в других реализациях языка .NET указанные вами типы могут не поддерживаться.

Ссылочные типы Переменные ссылочного типа (reference type) указывают на местоположение той или иной последовательности битов. Фактически они хранят лишь ссылки на сами данные. Во многом эти типы напоминают указатели на данные, которые хранятся в куче среды исполнения CLR и доступны только по ссылке. Запомните, что прямой доступ к основным данным недопустим; это нужно для того, чтобы «сборщик мусора» мог отслеживать активные ссылки и удалять данные после освобождения всех ссылок. Одна из возможных проблем с последовательными объектными моделями, такими, как .NET Framework, заключается в дополнительных расходах ресурсов. В частности, все объекты .NET Framework являются производными от базового типа System.Object. В таблице 3-2 перечислены методы объекта System.Object. 3 Зак 422


50 Таблица 3-2.

Глава 3 Методы System.Object

Метод

Описание

Equals(Obj)

Возвращает true, если Obj является тем же самым экземпляром, что и при вызове Equals no умолчанию. Этот метод разрешается переназначать для проверки тождества типов со значением

EqualsfObjA, Ob]8)

Возвращает true, если ObjA и ObjB — это один и тот же обьект

Finalize

Защищенный метод, вызываемый для освобождения ресурсов объектом перед «сборкой мусора». Реализация по умолчанию пуста, В С# вместо Finalize лучше использовать деструктор

CetHashCode

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

GetType

Возвращает обьект класса Туре, содержащий метаданные, относящиеся к объекту, метод которого вызван Защищенный метод, предоставляющий плоскую копию объекта. Плоская копия содержит только содержимое текущего объекта, без ссылок на другие объекты

MemberwiseClone

New Object ReferenceEquals (ObjA, ObjB) ToString

Метод создания объекта в Visual Basic .NET Метод создания объекта в С# Возвращает true, если ObjA и ObjB один и тот же экземпляр или оба пусты, то есть равны null Возвращает строку, представляющую объект, Например, для целого типа возвращается значение переменной

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


Объекты и языки ,NET Framework

51

Самоописываемые типы — тип переменной такого типа можно определить по ее значению. Самоописызаемые типы подразделяются на классы и массивы. Как и большинство классов в других языках и каркасах, класс в .NET Framework является контейнером свойств и методов объекта. Классы делятся на пользовательские классы, упаковочные типы и делегаты.

Интерфейс позволяет упаковывать описания набора функций.

• Указатель ссылается на значение, указывающее на другой объект. Значение этого типа не является объектом, поэтому по такому значению нельзя точно определить тип. (Для понимания большей части исходного текста в этой книге глубокие знания указателей не требуются.) Перечисленные типы не связаны с каким-то конкретным языком .NET — и в Visual Basic .NET, и в С# определен синтаксис для поддержки каждого из этих ссылочных типов.

Встроенные ссылочные типы Одним из встроенных ссылочных типов в Visual Basic .NET и С# является класс Object (в С# его название пишется строчными object). Оба типа базируются на описанном ранее типе System.Object. Типы со значениями можно применять во множестве ситуаций, они позволяют эффективно использовать ресурсы. Что произойдет, если требуется работать с таким типом, как объект? Например, метод ToString объекта System.Object часто используют для отладки. Речь идет об упаковке (boxing) переменных. Допустим, у вас есть переменная / типа int и требуется совершить какое-то действие с ее строковым представлением. Взгляните на такой фрагмент кода.

using System; class test { static void Main() { int i=5; object box=i; System. Console. WriteLine(box.ToStringO);


52

Глава 3 System. Console. W r i t e L i n e ( i . T o S t r i n g O ) ; System. Console. W r i t e l _ i n e ( b o x . GetTypeQ); System. Console. W r i t e Line (i.GetTypeO) ;

Сначала мы присваиваем целой переменной /значение 5. Затем объявляем объект box и присваиваем ему значение /. В этой строке переменная простого размерного типа упаковывается в объект, который ссылается на переменную ссылочного типа -вот почему этот процесс называется упаковка (boxing). На следующей строке выполняется вывод в консоль box.ToStringO (с применением метода System. Console. Writeiine}, где ToSthng — это метод объекта, В результате получаем то, что и ожидали: в консоли отображается число 5. Немного странно, что следующая строка также отображает в консоли число 5. Здесь объект явно не присутствует, однако вызов метода ToString объекта срабатывает, давая ожидаемый результат. Это возможно благодаря упаковке размерных типов (в данном случае целочисленной переменой /) в объекты в. NET Framework. Таким образом, метод вызывается для упакованной версии целой переменной. Тот же принцип используется в двух последующих строках кода для отображения типа объекта с помощью объектного метода GetType. Обратите внимание, что и явно упакованная версия переменной /, хранящаяся в объекте box, и созданная «на лету» упакованная версия соответствуют типу 5ystem./nt32. Ни один из рассмотренных методов обычно не используется (разве что для отладки), но сама возможность создавать объект, содержащий ссылку на любые другие типы, очень полезна. Упаковка размерных типов означает создание копии упаковываемого значения. Например, если добавить в рассматриваемый фрагмент кода такие строки:

1=12; Syst em. Console. W r i t e L i n e ( b o x , T o S t r i n g ( ) ) ; System. Console. W r i t e L i n e ( i . T o S t r i n g O ) ; Первая строка (она ссо!лается на ту копию переменной /, которая применялась в первом присвоении} отобразит в консоли 5, но вторая строка (она ссылается на упакованную копию перемен-


Объекты и языки .NET Framework

53

ной /') обратится к новому значению /, и поэтому з консоли отобразится число 12. Причина в том, что упаковке подвергается размерный тип. С другой стороны, при упаковке ссылочного типа объект ссылается на сам объект, а не на его копию. using System;

class intHolder : public int i; class test{ static void Main() 1 object box;

intHolder ih = new intHolder(); ih.i=22; box=ih; ih.i=99; System. Console, Write Line((( intHolder) box). i.ToString(J); System. Console. Write Line (ih. i.ToString ());

Оба вызова System. Console. WriteLine отобразят в консоли число 99, потому что box теперь ссылается на переменную ссылочного типа intHolder, а не на ее копию. Если intHolder объявить не какс/ass, а как struct, то первая строка кода выведет в консоль 22, потому что таково з этом случае значение копии переменной ih до ее упаковки в объект box.

Примечание В этих примерах используется С#, но эти же принципы применимы ко всем языкам .NET. Класс String, присутствующий как в Visual Basic .NET, так и в С#, предоставляет все необходимые возможности для обработки строк. Не менее важно то, что методы идентичны в обоих языках, Это же требование предъявляется ко всем языкам, которые будет поддерживать -NET. В следующем фрагмент кода демонстрируются лишь некоторые возможности класса String.


54

Глава 3

Public Module test Sub Hain() Dim s as String Dim i as integer s="This is a test System.Console.WriteLine(s & "|") s=s.Trim() System.Console.WriteLine(s & "|") S="46" 1=4

System. Console. WriteLinefi + System.Convert.Tolnt32(s)) End Sub End module В процессе исполнения этой небольшой программы з консоль выводится следующее;

This is a test This is a test I 50

|

Сначала создается и выводится в консоль строка с несколькими пробелами в конце. Далее вызывается метод Trim, и теперь строка отображается без пробелов между словом test и вертикальной чертой. После этого мы присваиваем переменой s значение равное строке «46». Обратите внимание, что при этом старая строка («This is a test») осталась неизменной, вместо этого создана копия. Строки неизменяемы — после создания их нельзя изменить напрямую. Если удобнее напрямую изменить существующую строку, а не создавать новую копию, рекомендуется использовать класс StringBuilder. Применение этого класса при каждом изменении строки наиболее уместно, когда требуется выполнить множество изменений в большой строке. В противном случае многократное выделение и освобождение большого блока памяти могут отрицательно сказаться на производительности. В конце строка приводится к типу Int32, суммируется с другим целочисленным значением, и полученный результат отображается в консоли. Большинство VB-программистов привыкли работать со строками именно так — присваивать значения переменным напрямую, полагаясь на системную поддержку таких операций, как удале-


Объекты и языки .NET Framework

55

ние ведущих и/или замыкающих пробелов. Однако в C/C + + все по-другому (несмотря на то, что многие программисты, работающие с C+ + , используют строковые классы библиотеки Standard Template Library и поэтому знакомы с подобными приемами). По сути, подобный код можно написать на С#, и что не менее важно, в этом случае при передаче строк между языками .NET не возникнет затруднений, возможных при использовании традиционных языков платформы Win32. В процессе работы с различными языками программирования на этой платформе, я обнаружил как минимум три разновидности строк: • строка с замыкающим нулем, которая очень часто применяется при программировании для Win32 и на C/C++; • строка Basic, или 6577?, очень часто применяется в Visual Basic и COM; • строка с байтом-префиксом, который хранит значение длины. Сама строка следует после этого байта. Такие строки очень популярны в Pascal. Можно также вспомнить о строках с замыкающим нулем, состоящих из 2-байтовых символов (Unicode). В .NET Framework предусмотрен межълзыковый стандарт для хранения строк.

Классы в.NET Framework В .NET Framework есть сотни классов, позволяющих делать то, для чего раньше требовалось прибегать к функциям API-интерфейса Win32 . В таблице 3-3 перечислены некоторые классы и предоставляемые ими возможности. Таблица 3-3. Объект

Некоторые классы .NET Framework Описание

Microsoft.Win32.Registry Операции с реестром System Array Поддержка массивов, в том числе поиск и сортировка System.Collections Поддержка работы с наборами. Включает такие упрощающие работу с данными классы, как Arrayiist, BitArray и Stack System.Data Доступ к данным, включая поддержку ADO.NET (более подробно ADO.NET рассматривается в главе 8)

(см. след, стр.)


г.*.

Глава 3

Таблица 3-3. (продолжение) Объект

Описание

5ysf em. DateTime

Поддержка работы с датами и временем

System. Diagnostics

Поддержка журналов событий и других отладочных операций, а также получения информации о процессе

System. Net

Поддержка работы с DNS (Domain Name System), cookies, Web-запросами и ответами

System .Net. Sockets

Поддержка сокетов TCP/IP. Механизм похож на WinSock в Win32, но более четко организован

System. Reflection

Поддержка управляемого просмотра загруженных типов и методов, а также создания и вызова типов

System. Threading

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

System.Web.UI

Поддержка создания элементов управления и страниц в Web-приложениях

System.Xml

Поддержка XML, в том числе спецификации DOM (Document Object Model) базового уровня 2 и протокола SOAP (Simple Object Access Protocol) версии 1.1

Операции, для которых все еще необходим API-интерфейс Win32 Несмотря на достаточно богатьш выбор объектов .NET Framework, остались области, в которых приходится «спускаться» на уровень API-интерфейса Win32 . Например, проецируемые в память файлы (memory mapped files, MMF) в .NET Framework в настоящий момент напрямую не поддерживаются. Файлы MMF позволяют легко организовать совместный доступ к данным из нескольких приложений. Я использовал такие файлы для взаимодействия Win32-npuложений, написанных на C + + и на Borland Delphi. В обоих языках применялся указатель, запись и чтение по которому выполнялось так же, как и в обычном указателе в памяти. Как в Visual Basic .NET, так и в С# предусмотрена возмож- сть вызова функций Win32 API, впрочем, как и любой другой стандартной динамически подключаемой библиотеки.


Объекты и языки .NET Framework

57

В таблице 3-3 упомянуты лишь некоторые возможности классов .NET Framework. До сих пор некоторые из представленных возможностей не поддерживались даже в стандартной среде Win32 (например, поддержка XML). Способ поддержки других вещей, например управление потоками, претерпел существенные изменения. Объекты для работы с потоками позволяют программистам, пишущим на Visual Basic, безопасно распределять исполнение программы между несколькими потоками. Поскольку эти объекты исполняются в управляемом контексте, они предоставляют все преимущества управляемого кода (безопасность и надежность) — программист может сосредоточиться на особенностях своей программы, что действительно необходимо при создании мощных приложений.

Основные сведения о Visual Basic .NET Редко компании масштаба Microsoft рискуют своими флагманскими продуктами, как это сделала Microsoft, выпустив Visual Basic .NET. В Visual Basic .NET в основном сохранилась та легкость программирования, которая и сделала Visual Basic популярным, однако в этой среде перестают работать практически все существующие программы. Более того, программистам, работающим с ASP и использующим VBScript (Visual Basic Scripting Edition), придется подучиться, чтобы воспользоваться всеми преимуществами Visual Basic .NET. Изменения в Visual Basic заставят закрыть рот критиков, которые пренебрежительно называли Visual Basic «игрушечным» языком. Больше всего нареканий у программистов, не любящих Visual Basic, вызывала обработка ошибок. Иногда принятую в Visual Basic обработку ошибок называют «On Error Goto Hell» («При ошибке идти к черту)?}. Обработку ошибок в Visual Basic можно настроить как надо, но на практике сделать это корректно очень трудно, и часто она работает из рук вон плохо. В VBScript обработка ошибок еще больше ограничена, и это расстраивало программистов, работающих с ASP. Изменения в обработке ошибок — всего лишь одна из нескольких областей Visual Basic, которая существенно улучшена в Visual Basic . N ЕТ, хотя и ценой утраты совместимости с существующими программами.


58

Глава 3

Старье — на помойку! Во многих отношениях Visual Basic стал жертвой собственного успеха. Существует шутка на тему, почему бог создал мир всего за семь дней: ему не нужно было приноравливаться к уже существующей платформе. Я думаю, эту шутку оценят члены команды в Microsoft, которая занимается разработкой языка Visual Basic. Внесение изменений в базовую среду разработки для многих Windows-программистов — дело тонкое. В каждую новую версию привносились новые особенности, но при этом обеспечивалась поддержка старого кода. В Visual Basic .NET эту традицию сломали. Есть несколько причин таких коренных перемен. Самая важная из них заключается в том, что базовой платформой для программ на Visual Basic .NET является не Win32, a .NET Framework. Одно это потребовало множества изменений. В .NET Framework поддерживается старый механизм обработки исключений типа «On Error Goto», однако, применив его, вы не сможете в полной мере использовать возможности нового каркаса. Прежде чем поговорить о новых особенностях, я расскажу о двух самых важных отличиях между Visual Basic 6.0 и Visual Basic .NET — операторе Set и стандартных соглашениях о вызове параметров. Оператор Set уходит в прошлое Ключевое слово Set и его использование часто приводили в замешательство новичков от Visual Basic. Например, когда требовалось создать экземпляр элемента управления ActiveX со свойством ProgID Foo.Bar, обычно писали такой код: Dim foo As Foo.Bar Set foo = New Foo.Bar

Создание объекта требует использовать ключевое слово Set. К сожалению, многие разработчики не до конца понимают, что такое объект с точки зрения Visual Basic. Я знаю не одного программиста, который возился с Set в тщетных попытках заставить свою программу работать. Иногда она начинала работать, поскольку проблема на самом деле заключалась в наличии или отсутствии Set, но часто настоящая причина оставалась скрытой и обнаруживалась лишь при внимательном просмотре исходного текста. Зачем вообще требуется Set? В Visual Basic 6.0 и в более ранних версиях объекты имели свойства по умолчанию, для которых не


Объекты и языки .NET Framework

59

требовался параметр. Если у объекта foo есть внепараметрическое свойство по умолчанию bar, то в отсутствие Set иногда возникает двусмысленность, как в этом коде:

Dim f as foo Dim о as Object foo=o В данном случае непонятно, кому присвоить значение «о»— foo.bar или foo. В Visual Basic .NET необходимость использования Set ликвидирована за счет отмены внепараметрических свойств по умолчанию. Более того, ключевое слово Set в Visual Basic .NET запрещено. Стандартные соглашения о вызове параметров Второй особенностью, которая потребует важных доработок исходного текста при переходе на Visual Basic .NET, можно считать изменения в способе передачи параметров в функции и процедуры. В предыдущих версиях Visual Basic передача всех параметров по умолчанию выполнялась по ссылке, то есть новая копия параметра не создавалась, а передавался указатель на место передаваемого параметра в памяти. Взгляните на такой фрагмент кода на Visual Basic 6.0: Private Sub Comfnand1_Click() Dim 1 As Long

Dim OldL As Long Dim t As Long 1 = CLng(TimerO) Oldl. = 1

t = CallingByReference(l) MsgBox "1 was " & CStr(OldL) & " but is now " & 1 End Sub Function CallingByReference(Ref As Long) As Integer

Ref = Ref Mod 60

CallingByfteference = Ref End Function Выполнив эту программу в любое время (после полуночи), мы получим два различных значения (рис. 3-2).


60

Глава 3

Рис. 3-2. Диалоговое окно с сообщением при вызове CallingB yReference Возможность изменения параметров чаще всего оказывается полезной, но иногда она приводит з замешательство новичков, Например, начинающий программист, взглянув на этот исходный текст, не заметит взаимосвязи между переменной / и переменной Ref в функции CallingByReference. Конечно, в Visual Basic 6.0 и в более ранних версиях можно явно указать, что параметр передается по значению. Вот функция на Visual Basic 6.0, в которой выполняется вызов по значению: Function CallingByValue(fiyVal Ref As Long) As Integer Ref = Ref Mod 60 CallingByValue = Ref End Function В этом случае значение переменной / останется неизменным (рис. 3-3).

Рис. 3-3. Диалоговое окно с сообщением при вызове CallingByValue Считается хорошим тоном явно объявлять соглашения о зызозе — это позволяет избежать неприятных ситуаций. Это и есть стандарт для всех будущих программ на Visual Basic .NET.

Примечание Если параметр достаточно объемный, передача по ссылке оказывается более эффективной, даже если функция не изменяет значение параметра.


Объекты и языки .NET Framework

61

Дорогу новому! Отказ от поддержки старых программ станет для многих самым большим откровением, касающимся Visual Basic .NET, но намного важнее улучшения в самом языке. Проблемы с совместимостью — это временная проблема, а выгода от новых возможностей будет ощущаться еще долго. Улучшения потрясут основы «мироздания» тех разработчиков, кто привык к ограничениям работы VBScript в ASP. Наследование и полиморфизм В последних версиях из Visual Basic с переменным успехом пытались сделать объектно-ориентированный язык. Чтобы называться объектно-ориентированным, язык должен отвечать трем основным требованиям. Во-первых, поддерживать полиморфизм, то есть вызываемый метод объекта зависит от типа самого объекта, во-вторых, поддерживать инкапсуляцию, то есть наличие четкого разделения между представлением объекта и особенностями его внутренней структуры. Например, если объект на выходе дает набор строк, то детали внутренней реализации недоступны, то есть нельзя узнать хранится ли набор в массиве, связанном списке или стеке. Наиболее же важным требованием является наследование — возможность получения одного типа из другого. Например, имея простой класс: Public Class Base Public Function foo System.Console.WritelineC"Base Foo") End Function End Class можно создать класс-потомок: Public Class Derived Inherits Base Public Function bar System.Console.Writeline("De rived Bar") End Function End Class Если создать экземпляр класса Derived и вызвать функцию foo, в консоли отобразится текст «Base Foo».


Глава 3

62

Наследование удобно применять при повторном использовании кода. Это намного лучше, чем применение такой популярной в прошлом простой операции «вырезать и вставить». Допустим, нужно создать набор классов, отображающих фигуры. V фигур есть общие характеристики, такие, как местоположение, длина и ширина, и операции, производимые над фигурами, например Draw (рисование) и Move (перемещение). Наследование позволяет создать иерархию фигур, изначально полученных из класса Shape, упрощенная форма которого выглядит так: Mustlnherit Class Shape Private myX as Integer Private myY as Integer Public Sub New() myX = 0 myY = 0 End Sub

Public Property X Get X = myX End Get Set

myX = Value Draw() End Set End Property Public Property Y Get Y = myY End Get Set myY = Value Oraw() End Set End Property MustOverride Function Draw() End Class Class Square Inherits Shape Overrides Function Draw() ' Здесь размещается реализация метода Draw ' класса Square (квадрат) End Function End Class


Объекты и языки .NET Framework

63

В этом простом примере при создании экземпляра s класса Square установка значения свойства s.X вызовет метод Set этого свойства, как определено в классе Shape, а затем вызовет метод Draw класса Square. Более того, если объект 5, основанный на классе Square, передается методу, который воспринимает объект Shape в качестве параметра, и оттуда вызывается метод Draw, то вызывается метод Draw именно объекта Square. Классы с одним и тем же именем могут вести себя по-разному. Способность языка определить, как поведет себя класс в зависимости от типа объекта, называется полиморфизмом, Несколько слов о множественном наследовании Visual Basic .NET не поддерживает множественное наследование — в классе можно использовать только одно ключевое слово Inherits. В некоторых объектных моделях (особенно в C + + ) множественное наследование используется как способ получения объектов, наследующих нескольким классам, например объект Dog (собака) может наследовать одновременно и Animal (животное) и Pet (домашний любимей,). Единичное наследование не такая уж и трагедия, кроме того, оно предотвращает появление двусмысленности методов. Например, если Dog наследует классам Animal и Pet и в обеих иерархиях присутствует метод MakeNoise (гавкать, шуметь), возникает двусмысленность: метод какого класса вызывать. Есть много способов обойти ограничение единичного наследования. В данном случае, Animal можно определить как базовый класс, Pet реализовать как класс-наследник Animal, a Dog — как наследник Pet. Это не множественное наследование, так как на каждом уровне присутствует только одно ключевое слово Inherits. [Такое решение непригодно для класса PetRock (любимый камешек), поскольку Pet Rock можно считать домашним любимцем, но это не животное]. Другое решение — создать класс Animal и его потомок Dog, a затем в последнем реализовать интерфейс Pet. Интерфейс похож на класс за исключением того, что он содержит только методы, и эти методы не реализуются на уровне интерфейса. Реализовать интерфейс в классе можно простым указанием с помощью ключевого слова Implements и определением методов, ука-


64

Глава 3

занных з интерфейсе. Методы могут реализовывать несколько интерфейсов. Структурная обработка исключений Существую две основные модели обработки ошибок. Первая предполагает возможность любой функции сообщать об ошибках, причем ответные действия на основании возвращенной функцией ошибки возлагается на код, который вызывает функцию. Вот пример: Ret = SomeFunc(SomeParam) If Ret = 0 then 1 Произошла ошибка, ее необходимо обработать. End If 1 Продолжение исполнения.

V этой модели обработки ошибок есть несколько недостатков. При частом использовании обработка ошибок «смешивается» с получением результатов.. Так, в языке программирования С функция /open возвращает указатель на файл, который используют другие функции, например fgets. Если открыть файл не удается, fopen возвращает не указатель на файл, a NULL, указывающий на то, что произошла ошибка. Таким образом, возвращаемое функцией значение является описателем файла или сигналом об ошибке. Многие разработчики прекрасно справляются с неоднозначностью возвращаемого значения, но некоторые не всегда вспоминают о необходимости проверки того, не возвращено ли в качестве результата сообщение об ошибке. На практике большинство программистов, пишущих на С, выполняют такую проверку при вызове fopen, поскольку вероятность ошибки достаточно высока. Однако они же часто не проверяют ошибки в таких функциях, как fputs, поскольку сбои в функции, работающей с корректным указателем на файл, происходят довольно редко. Таким образом, при записи в файл иногда происходит сбой, например, изза переполнения диска или по другой причине, которая остается незамеченной. Другая модель обработки ошибок •— обработка исключений. В такой системе ошибка вызывает исключение, и это исключение перемещается в стеке, пока его не обработает соответствующий обработчик. Несмотря на то, что в Visual Basic поддерживается


Объекты и языки .NET Framework

65

такой вид обработки исключений с помощью оператора On Error, предлагаемая для этих целей форма не очень удобна. Возможности программистов, работающих с VBScript в ASP, еще более ограничены, поскольку в VBScript отсутствуют даже те возможности управления обработкой исключений, которые есть в Visual Basic или VBA (Visual Basic for Applications). Предпочтительным видом обработки исключений является структурная обработка исключений. Хотя это скорее особенность .NET Framework, чем самого Visual Basic .NET, этот важный шаг вперед позволит разработчикам создавать более устойчивые и надежные приложения. Общая форма структурной обработки исключений выглядит так:

Try ' Код, которой способен инициировать исключение Catch e As Exception ' Обработка ошибки Finally 1

Код, который должен.выполняться ' независимо от того, возникло исключение или нет End Try

Код, при выполнении которого возможно исключение, располагают внутри блока Try. Возможно, некоторые или даже все исключения не удастся обработать на данном уровне. В таком случае исключение перебрасывается (с помощью ключевого слова Throw), но код в блоке Finally все равно выполняется. Например, если в блоке Try открывается подключение к базе данных, з блоке Finally придется закрыть это подключение, поскольку этот блок выполняется в любом случае. В блоке Finally может потребоваться проверить, действительно ли открыто подключение к базе данных, поскольку исключение может возникнуть до создания подключения. Блок Finally позволяет освобождать ресурсы, занятые в блоке Try, только в одном месте, вместо того чтобы писать этот код несколько раз — при нормальном исполнении и по разу в каждом блоке Catch. Перегрузка функций Перегрузка функций позволяет создавать несколько версий одной функции, но с различными параметрами. Например, в методе передачи строки в браузер можно объявить несколько функ-


66

Глава 3

ций с именем Write — одна версия будет принимать в качестве параметра строку, другая —целое число, а третья —объект типа DateTime. Программист, работающий с VBScript, может спросить, зачем это все нужно. В предыдущей объектной модели ASP можно, к примеру, вызвать метод Response.Write со строкой, целым числом или датой, и это работает, как и ожидалось. Однако тонкое различие все-таки есть. В VBScript все переменные имеют тип Variant, который как хамелеон приспосабливается к тому, что содержится в переменной. Метод Response .Write просто получает то, что ему передается, и записывает результирующую строку в HTMLпоток. Отличие перегрузки функций в том, что в зависимости от типа аргумента вызываются разные методы Write. При вызове метода Write с аргументом, который нельзя явно преобразовать в один из типов, ожидаемый одной из перегруженных функций, во время компиляции произойдет ошибка. Перегрузка также применяется для корректного расширения существующих систем без внесения изменений в существующий код. Например, если существующий метод Write воспринимает в качестве параметра только строку, а в новых обстоятельствах нужно выводить эту строку в цвете, то создается новый метод Write, который воспринимает оба параметра — строку и цвет. Исходный текст существующего метода без проблем заменяется на вызов нового метода с передачей дополнительного параметра — цвета по умолчанию. Существующих пользователей не придется переучивать, а расширенный метод Write можно использовать в новых программах. Строгий контроль типои переменных Одна из самых больших перемен для программистов, работающих с ASP и переходящих с VBScript на Visual Basic .NET, заключается в необходимости строгого контроля типов. Несмотря на то, что в VBScript предусмотрена возможность объявлять переменные, им нельзя назначить определенный тип. Вот обычный на то время фрагмент кода:

Dim X X="Hello There"


Объекты и языки .NET Framework

67

Х=7

Response.Write(X) В данном примере переменной X присваивается строка, а затем — целое число. В результате метод Response.Write выводит строку, содержащую число 7. Это возможно, потому что все переменные в VBScript одного типа — Variant. Для выявления возможных ошибок при преобразовании типов в Visual Basic .NET добавлена новая директива Option Strict. Она строже, чем Option Explicit. В режиме Option Strict Visual Basic .NET генерирует ошибки, когда преобразование типов приводит к потере данных, переменная не объявлена или в случае позднего связывания переменной. Для большинства программистов, работающими с другими языками, это не новость, а для программистов Visual Basic .NET, пытающихся создавать профессиональные надежные приложения, это огромный шаг вперед. Сокращенное вычисление выражений Другая проблема, с которой встречаются программисты, пишущие на C/C++ и переходящие на Visual Basic, — метод вычисления логических выражений. Допустим, на ASP-странице есть следующий код: While rs.EOF=False And rs("Grouping")=thisGroup ' Здесь мы что-то делаем со всеми членами группы thisGroup Mend Программисты, давно работающие с С и C++ и имеющие стереотипы, решат, что если значение rs.EOF равно True, то на этом вычисление выражения завершится. В VBScript и Visual Basic 6.0 это не так. Даже если rs.EOF обратится в True, выражение rs(«Groupmg») все равно вычисляется, и это вызывает ошибку. Очевидно, что, когда rs.EOF равно True, результат выражения заведомо равен False, независимо от значения других частей выражения. В Visual Basic .NET введены два новых логических оператора (AndAiso и OrElse], которые применяются для сокращенного вычисления выражений. В предыдущем примере можно заменить оператор And на AndAiso:


68

Глава 3

While rs.EOF=False AndA:.so rs("Grouping"}=thisGroup 'Здесь мы что-то делаем со всеми членами группы thisGroup Wend

Если значение rs.EOF равно True, остальные части выражения не вычисляются, поскольку понятно, что значение выражения в целом не равно True. Такой порядок оценки можно применять для пользы дела, упорядочивая части логического выражения от менее к более сложным. Однако следует помнить, что при сокращенном вычислении некоторые части выражения не вычисляются, что может вызывать побочные явления. Операторы And и Or работают так же, как и раньше в Visual Basic 6.0 и более ранних версиях, вычисляя все части предиката. Прочие изменения В этом разделе перечислены другие новинки Visual Basic .NET, • Отсчет номеров элементов во всех массивах начинается с нуля. Существуют способы обойти это ограничение, используя классы .NET Framework, но нумерация массивов в самом языке всегда начинается с нуля. При объявлении массива становится заметным интересное нововведение, специально разработанное для облегчения переноса существующего кода. Взгляните на следующее объявление: Dim a(5) as Integer Здесь создается массив из шести элементов, от а(0) до а(5). Это позволяет разработчикам продолжать работать с массивами, как и раньше, но те, кто разрабатывает межъязыковые компоненты, должны знать эту особенность и тщательно документировать поведение массивов в своих компонентах. • Оператор Option Вале не поддерживается. • V массивов нет фиксированного размера. Можно объявлять массивы определенного размера, массивы без размера и изменять размер с помощью ключевого слова New или объявить, инициализировать и определить размер массива в одном операторе, например: Dim W e e k ( ) As Integer = {1, 2, 3, 4, 5, 6, 7}


Объекты и языки .NET Framework

69

В Visual Basic .NET для изменения размера массивов применяют оператор ReDim. В Visual Basic 6.0 с массивом точного размера этого делать нельзя. •

Нельзя точно определить длину строки.

• Нельзя использовать оператор ReDim для объявления переменной — сначала ее необходимо объявить оператором Dim. • Денежный тип данных Currency больше не поддерживается вместо него рекомендуется использовать Decimal. • Оператор Туре больше не поддерживается. Вместо него следует использовать конструкцию the Structure...End Structure. Каждому члену Structure необходимо назначить модификатор доступа: Public, Protected, Friend, Protected Friend или Private. Можно также применять оператор Dim, в этом случае члену структуры назначается тип доступа Public. • Для объявления нескольких переменных одного типа достаточно перечислить их через запятую на одной строке и один раз указать тип, как в следующем примере: Dim I, J as Integer В Visual Basic 6.0 переменная / получит тип Variant, a j— целый тип, а в Visual Basic .NET обе переменные будут целого типа. •

V переменных, объявленных внутри блока, область видимости не выходит за рамки блока и не распространяется на всю процедуру. Таким образом, если объявить переменную / внутри блока While, она будет видима только внутри этого блока. Нужно отметить, что время жизни переменной такое же, как и у процедуры, поэтому, если код блока, в котором объявляется переменная, выполняется несколько раз, при каждом выполнении этого кода переменную следует инициализировать.

• При вызове процедур даже с одним параметром необходимо использовать круглые скобки. • Вместо While и Wend в Visual Basic .NET используются While и End While. Wend больше не поддерживается. • Функция IsNull заменена на IsDBNull, a IsObject — на 1$Reference.


70

Глава 3

Основные сведения о языке С# Многие программисты, работающие с С и С+ +, с особым нетерпением ожидали возможности использовать С# в ASP.NET. С# (произносится как «си шарп») —это новый язык, созданный специально для работы с .NET Framework. В частности он разработан для надежной работы программ в среде с управляемым кодом. Использование указателей — это одна из причин, по которой программы на С и C + + не могут нормально функционировать в такой среде. Хотя оба языка позволяют программировать и без указателей, сделать это чрезвычайно трудно. Как я уже говорил з главе 2, указатели подрывают безопасность кода и поэтому их не рекомендуется применять в ASP.NET, за исключением чрезвычайных обстоятельств. Синтаксис С# очень похож на синтаксис C + + , но в то же самое время он позволяет использовать все особенности .NET Framework, в том числе библиотеку классов и «сборку мусора». Блоки заключаются в фигурные скобки, и многие ключевые слова (while, for, if и т.д.) работают точно так же, как и в C++. Это сходство превращает С# в удобную стартовую площадку для программистов, пишущих на C++ и планирующих работать в ASP.NET.

Различия C + + и С# Понятно, что если бы С# был в точности похож на C++, незачем его и создавать. Требовался же язык программирования, похожий на C + + , но более легкий и надежный. Для многих таким языком стал Java, но и он имеет ряд недостатков, свойственных C + + , но устраненных в С#. Поскольку основным языком программистов, работающих с С+ + и переходящих на разработку в среде ASP.NET, станет С#, я расскажу о различиях между C++ иС#. Более безопасное управление памятью

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


Объекты и языки .NET Framework

71

прещает программе повторно использовать этот указатель, так как он точно не указывает на NULL, а на только что освобожденный участок памяти. В такой ситуации часто возникают ошибки, которые трудно «отловить» при отладке. Иногда они появляются при определенных обстоятельствах, а при отладке их не видно кошмар для программиста, работающего с C + + . Проблему решает переход на С#. В С# не используется явное освобождение созданных объектов — механизм «сбора мусора» отслеживает ссылки на управляемые объекты. Объект становится доступным для удаления, когда больше не остается каких-либо ссылок на него. Такой способ более безопасен, чем методы, используемые в СОМ-объектах, когда программистам приходится самостоятельно обеспечивать подсчет ссылок. С# не поддерживает указатели в безопасном или управляемом коде. В С# поддерживается механизм ссылок при передаче функциям параметров, который действует как система указателей, однако исключение указателей из безопасного кода снижает вероятность некорректного доступа к памяти. Отсутствие шаблонов Программистов, пишущих на C++ и собирающихся перейти на С#, несомненно разочарует отсутствие шаблонов. Они позволяют создавать параметризированные типы. Например, после создания класса массива целых чисел иногда требуется класс массива чисел с плавающей точкой. Шаблоны предлагают простой способ решения этой задачи без копирования и вставки исходного текста из класса в класс. Шаблоны в общем-то действуют так же, но всю работу компилятор выполняет за кадром, то есть программисту об этом заботиться не нужно. Очень маловероятно, что в С# будет предусмотрена поддержка шаблонов. В настоящее время разработчики С# рассматривают другие альтернативы общего решения задач внедрения без использования шаблонов. В .NET Framework есть классы массивов, поэтому описанная задача не должна вызывать особых затруднений. Однако в других случаях определенное общее решение не помешало бы.


72

Глава 3

Отсутствие множественного наследования Как u Visual Basic .NET, C# не поддерживает множественное наследование, и это также не создает особых проблем. В С# (и в Visual Basic .NET) можно создавать класс, реализующий несколько интерфейсов. Обычно этого достаточно для решения задачи. Многие программисты,, работающие с C + + , прекрасно чувствовали себя и без множественного наследования, и я уверен, что его отсутствие не сильно расстроит пишущих на С#. Отсутствие глобальных функций В отличие от C + +, который изначально задуман как язык, позволяющий программистам естественно перейти к использованию классов и других особенностей объектно-ориентированного программирования, в С# большинство приемов объектно-ориентированного программирования составляют «кровь и плоть» языка. После выхода в свет C + + для преобразования практически всех программ с С на C + + достаточно было просто изменить расширение файла с .с на .срр и перекомпилировать программу. Конечно, это были ненастоящие программы C++, но описанная процедура по крайней мере позволяла программистам воспользоваться такими преимуществами компиляторов C++, как вывод подробных предупреждений, например, об использовании функций без предварительного объявления. Переход на С# принципиально отличается. Например, во всех книгах или учебных пособиях по программированию на С и C + + на протяжении последних 20 лет публикуется программа «Hello World», исходный текст которой выглядит примерно так: main() < printf("hello, world\n">; 1 Эту программу можно скомпилировать (и исполнить) с применением большинства компиляторов С или C+ + , однако это не корректная программа на С#. Все функции в типичной программе на С# являются методами класса. В С# версия «Hello World» должна выглядеть приблизительно так:

Public class Hellol


Объекты и языки .NET Framework

73

public static void Main() [ System. Console. Writ eLine( "Hello, World! ");

Все стандартные консольные приложения С# используют з качестве точки входа метод Main некоторого класса. На практике это не создает проблем в большинстве программ ASP. NET, но важно помнить, что глобальных функций не существует — есть только методы класса. Отсутствие макросов препроцессора В С u C + 4- очень активно использовались макросы, интерпретируемые препроцессором. Препроцессор обрабатывает исходный текст перед компиляцией и при наличии макросов меняет отдельные строки. Препроцессор чрезвычайно удобен, однако иногда его использование вызывает неполадки. В С# отсутствует отдельно выделенный препроцессор, но это никак не отражается на выполнении директив препроцессора. Директивы препроцессора в основном те же, что и в C/C++, например #iff #eise и #endif. Кроме того, допускаются директивы Visual Studio .NET, такие, как #region и #endregion. Однако директива #ifdef отсутствует.

Что можно делать в С#, но нельзя — в Visual Basic .NET Наиболее часто в телеконференциях Usenet спрашивают, что же такое позволяет делать С#, что недоступно в Visual Basic .NET. На момент написания этих строк в С# есть только одна важная особенность, которая отсутствует в Visual Basic .NET, и несколько, которые обязательны в С#, но необязательны в Visual Basic .NET. Перегрузка операторов В разделе, посвященном Visual Basic .NET, я упоминал о перегрузке функций, то есть поддержке нескольких функций с одним именем, но с разным списком параметров. В С# поддерживается этот вид перегрузки, но, кроме того, возможна перегрузка операторов, которая отсутствует в Visual Basic .NET. Она позволяет создавать метод, который вызывается при использовании таких операторов, как +,-, ++ (приращение) или — (отрицательное при-


74

Глава 3

ращение). Перегрузка операторов напоминает перегрузку операторов в C + + , но есть и отличия. В таблице 3-4 перечислены операторы с указанием возможности перегрузки в С#. Таблица 3-4. Операторы С# и возможность их перегрузки Операторы

Тип оператора

Возможность перегрузки

+,-, !, ~, ++, true, false

Унарный

Да

+, -, *, /, %, &,

Бинарный

Да

— — , ! — , <, >,

Сравнение

Да, но только попарно. Например, если перегружен оператор = =, то оператор != также должен перегружаться

&&,

Логическое условие

Нет, хотя одинарные операторы & и перегружать можно

II

Индексы массива

Нет, но тот же результат можно получить за счет использования индексаторов. Этот оператор можно применять, в частности, для создания виртуального массива

Приведение типов

Нет, но тот же результат можно получить за счет использования операторов преобразования (явного и неявного)

+ --*-. =//

Присвоение

Нет, хотя возможно использование соответствующих перегруженных операторов, например в Ч- = используется оператор +

=, запятая (,), ?:, ->, new, is, sizeof, typeof

Другие

Нет

_

*

/

%=,&=, |=,

Недавно по телевизору я видел рекламу: группа людей с любопытством ожидает сноса большого здания — одного из многих других; после запланированного взрыва, когда пыль улеглась, неожиданно руководитель работ подает сигнал — и несколько близлежащих домов также рушатся. На вопрос репортера; «Зачем?», руководитель работ отвечает: «V нас оставалось немного лишнего динамита, и мы решили, что нечего ему пропадать зря».


Объекты и языки .NET Framework

75

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

!

public public public public

{

int red=0; int green=0; int blue=0; MyColor(int red,int green,int blue)

this.red=red; this.green=green; this,blue=blue;

}

public static MyColor operator +• (MyColor c1, MyColor c2) { return new MyColor(c1.red+c2.red, c1.green+c2.green, c1.blue+c2.blue); } public static void MainO { MyColor red = new MyColor(255,0,0); MyColor green = new MyColor(0,255,0); MyColor yellow = red + green; System. Console. NriteLlne("RGB of yellow={0}, {1}, {2}", yellow,red, yellow.green, yellow.blue); I

}

Это не ахти полезный пример перегрузки операторов —он просто иллюстрирует, как именно она происходит. В этом примере оператор «плюс» (+) перегружается в классе MyColor. Затем суммируются два экземпляра этого класса — с 7 и с2. При перегрузке операторов рекомендуется применять правила, полученные эмпирическим путем1. Из книги Douglas J. Reilly «Computer Language»,1992. Стр. 57.


76

Глава 3

• Применяйте стандартные математические операторы (+, -, * и /) для всех классов, представляющих числа. Популярный пример — класс комплексных чисел. • Применяйте перегруженные операторы для тех целей, которые пользователи от вас ожидают. Например, очевидно, что должен делать оператор присвоения со строками. •

Помните о представлениях пользователей, что некоторые операторы противоположны по функциям (например, пары + и -, * и /). Перегруженные операторы должны также выполнять противоположные действия.

• Не идите против ожидания и естественных убеждений пользователей. Создавать перегруженный оператор «плюс» (+) для вычитания глупо, и :это самый простой пример того, как поступать не нужно. Но иногда сложности спрятаны глубже. Например, какого результата ожидать от применения оператора приращения (++) к строке «Hi there»? Может, «i there')? А как насчет «IjSuidsd» (числовое представление каждого ASCIIсимвола увеличено на единицу)? Если не очевидно, что именно должен делать оператор, не создавайте его. • Не перегружайте оператор для изменения данных, если исходный оператор их не изменяет. Так, не стоит перегружать оператор сравнения, например оператор равенства {= = ), чтобы он изменял хотя бы одну из частей при проверке на равенство. • Создайте метод, который позволит пользователям языков .NET, не поддерживающих перегрузку операторов, получить ту же самую функциональность. Принудительное раннее связывание Программисты, пишущие на C + + , уверены, что переменные всегда объявляются и имеют определенный тип. Исторически сложилось так, что пишущим на Visual Basic это делать не обязательно, хотя опытные программисты всегда использовали режим Option Explicit для объявления переменных. До выхода Visual Basic .NET объявление переменных не только было необязательным, но и не всегда предоставлялась возможность четко определить тип переменных. В Visual Basic .NET введена новая директива Option Strict, которая предотвращает неявные преобразования, а также позднее


Объекты и языки .NET Framework

77

связывание. Последнее происходит, когда после создания объекта его тип определяется только во время исполнения. Такой подход иногда оказывается полезным, но, например, в СОМ-объектах позднее связывание почти вдвое увеличивает объем информации, которой обмениваются клиент и СОМ-объект. Определение типа используемого объекта на ранней стадии (во время компиляции) позволяет компилятору создавать более быстрый код.

Примечание Проблема с директивами Option Explicit и Option Strict в том, что их легко забыть. К счастью, в ASP.NET предусмотрен способ принудительного их использования от приложения к приложению. Более подробно об этих возможностях вы узнаете в главе 4. В С# директивы Option Explicit и Option Strict не нужны. Точнее, в этом языке нельзя использовать переменные без предварительного объявления их самих и их типа. Я читал обзоры о системах разработки динамического информационного наполнения для Интернета, в том числе и об ASP, в которых возможность использования переменных без объявления рассматривалась как преимущество. В ASP.NET это допустимо при разработке в Visual Studio .NET, однако я настоятельно советую вам использовать директиву Option Strict при создании исходных текстов на Visual Basic .NET. С# не поддерживает позднее связывание, , пожалуй, это единственное, в чем программы на С#, без сомнения, превосходят программы на Visual Basic .NET. В общем, все языки программирования .NET обеспечивают более или менее одинаковую производительность. В случае с Visual Basic .NET и С#, аналогичные по функциям программы преобразуются в аналогичный MSIL-код и поэтому в среде .NET Framework будут исполняться с практически одинаковой скоростью. Быстродействие сложных программ на Visual Basic .NET, где, возможно, непреднамеренно применено позднее связывание, окажется немного ниже, чем программы на С#, выполняющие те же задачи.


78

Глава 3

Заключение Из этой главы вы узнали почти все, что нужно, для уверенной работы с ASP.NET. Я надеюсь, что короткое знакомство с типами, объектами и стандартными языками .NET Framework поможет зам. В следующей главе я расскажу о программировании в среде ASP.NET. Если зы хорошо знакомы с HTML, трудностей возникнуть не должно, если же нет, я рекомендую ознакомиться с приложением Б. В нем изложены базовые сведения о HTML, без них вам будет сложно понять материал следующих глав. Недовольно вводных слов! Открывайте-ка главу 4 —приступим к программированию приложений ASP.NET!


Глава 4

После того как вы решили создать динамическое информационное наполнение для Web (а это произойдет рано или поздно), вам придется выбрать необходимый для этого инструментарий. В глазе 1 вы узнали о некоторых традиционных возможностях, таких, как CGI, ISAPI и ASP. Среда ASP.NET — новейшее средство разработки динамических Web-приложений, и в этой главе изложены основные понятия, необходимые для работы в ней. Разработка в ASP.NET зо многом похожа на традиционную ASP-cpegy, но есть много отличий. Для опытных ASP-разработчиков я специально отметил отличия между ASP и ASP.NET.

Здравствуй, мир ASP.NET! В пособиях по языкам программирования почти всегда приводят пример программы, выводящей сообщение «Hello World» («Здравствуй мир»). В главе 1 — кратком введении в ASP — показан типичный пример такой программы (листинг 4-1).

<% Option Explicit Я> <HTML> <HEAD> <TITLE>Hello ASP World</TITLE> </HEAD> <BODY>


80

Глава 4

<CENTER> Dim x For x=1 to 5

Response.Write("<FONT size=" & x) Response.Write(">Hello ASP World</FONT><BR>" & vbCrLf) Next

</CENTER> </BODY> </HTML> Листинг 4-1. Простейшее приложение SayHelloASP.asp

Пример на С# В листинге 4-2 показан исходный текст программы ASP.NET на С#, выполняющей туже задачу. <5(@ Page Language="C«" %> <HTHL> <HEAD> <TITLE> My First ASPX Page </TITLE> </HEAD> <BODY> <CENTER> int loop; String s=""; for ( loop=1 ; loop<=5 ; loop++ }

I

s=s + String,Format( "<FONT SIZE={OJ>Hello ASP.NET World</FONT><BR>", loop);

I

Message.InnerHtml=s;

%>

<SPAN id="Hessage" runat=server/> </CENTER> </BODY> </HTML> Листинг 4-2. Приложение SayHelloASPDOTNET.aspx на С#


Основы разработки приложений в ASP.NET

81

Отличия ASP.NET В ASP имена файлов имеют расширение .asp, а з ASP.NET - - .aspx. (V последних могут быть и другие расширения, но .aspx наиболее близок расширению в ASP.) ASP и ASP.NET-файлы могут сосуществовать бок о бок на Web-сайте, однако при этом у них не будет общих параметров приложений или информации о сеансах работы. Лучше хранить ASP и ASP.NET-приложения в разных каталогах, а взаимодействие между ними осуществлять с применением URL-аргументов или общей базы данных. В листинге 4-2 вы найдете много отличий версии ASP.NET от программы в ASP (листинг 4-1). Во-первых, язык: страница написана на С#, а не на VBScript или одной из версий Visual Basic. В перзой строке вместо используемой на ASP-страницах директивы Option Explicit, в файле SayHelloASPDOTNET.aspx указана директива Page, она также определяет язык, в данном случае — С#. Так как пример написан на С#, особого принуждения к объявлению переменных с помощью директивы Option Explicit не требуется, впрочем, она в этом языке и не поддерживается, У директивы Page есть несколько важных атрибутов, которые вам следует знать. Основные перечислены в таблице 4-1. Таблица 4-1. Атрибут Buffer

4 Зак 422

Атрибуты директивы Page Описание Управляет буферизацией HTTP-ответов. Если атрибут установлен в true (по умолчанию), буферизация страницы включена, а значение false отключает буферизацию. В общем, предварительная буферизация до получения браузером всей страницы позволяет улучшить производительность работы со страницей, но при этом отображение страницы откладывается, пока не загрузится вся информация. Сложные страницы часто размещают в HTML-таблицах. Если вся страница представлена в виде таблицы, она не отображается, пока не получен закрывающий элемент таблицы, поэтому явная буферизация таких страниц практически никак не влияет на их отображение (см. след, стр.)


Глава 4

Таблица 4-1.

(продолжение)

Атрибут

Описание

ContentType

Определяет тип содержимого HTTP-ответа в виде стандартного MIME-типа. В частности, если дан ный атрибут установлен в Application/MSWord, для открытия документа вызывается приложение, сопоставленное DOC-файлам, а не браузер

EnableSessionState

Если атрибут установлен в true, поддерживается состояние сеанса, при значении атрибута Readonly разрешается чтение, но не редактирование состояния сеанса, а при значении false состояние сеанса не поддерживается

EnableViewState

Определяет (в зависимости от значения — true или false), сохраняется ли состояние страницы (view state) между запросами

ErrorPage

Определяет URL-адрес для переадресации при возникновении необрабатываемой ошибки

Explicit

При установке атрибута в true в Visual Basic .NET включается режим Option Explicit, в котором требуется обязательно объявлять переменные

Inherits

Определяет CodeBehind-класс, которому наследует страница. Допустимы любые имена классов, производных от класса Page

Language

Задает язык, используемый во всех встраиваемых участках кода (обрамленных значками <% и %>). Допустимо указывать любые языки, поддерживаемые .NET, в том числе Visual Basic, C# или [Script .NET

Strict

Если атрибут установлен в true, в Visual Basic .NET включен режим Option Strict, в котором требуется объявлять переменные и запрещено приведение типов с потерей данных

Trace

Определяет (в зависимости от положений true или false], включена ли трассировка. По умолчанию атрибут установлен в false. Применяется для отладки программ

Transaction

Определяет, поддерживаются ли на странице транзакции. Допустимые значения NotSupported (по умолчанию), Supported, Required или RequiresNew

WarningLevel

Определяет уровень диагностики, при котором компьютер прекращает компиляцию страницы. Принимает значения из диапазона 0—4


Основы разработки приложений в ASP.NET

83

На ASPX-странице разрешается размещать лишь одну директиву. Строки, следующие за директивой Page в файле SayHelloASPDOTNET.aspx, представляют собой стандартный HTML-код. Отрезок программы, который открывается значком <% и закрывается значком %>, написан на языке С#. Программисты, пишущие на C/C+ + , без труда поймут код сценария 0 этом блоке. Объявляются две переменные: целое число loop и строка s. В цикле for размер шрифта изменяется от 1 до 5. В переменной s я размещаю весь создаваемый HTML-код. Для вывода информации вместо команды Response .Write (очень популярный метод на ASP-страницах) я задал значение свойства InnerHtml объекта Message. Message — это HTML-тэг <SPAN>, объявленный далее в HTML-коде: <SPAN id="Message" runat=server/>

Отличия ASP.NET Если здесь для вывода текста попытаться применить команду Response.Write, исходный текст не скомпилируется. Это не ограничение технологии ASP.NET, а различие между С# и Visual Basic --в первом важен регистр, а во втором — нет. Обратите внимание, что для ссылок на HTML-тэг SPAN используется атрибут id. Другая важная особенность этого тэга — атрибут runat, показывающий, что он исполняется на сервере. В ASP.NET-приложениях часто применяются элементы управления, исполняемые на стороне сервера. Также следует отметить отсутствие закрывающего тэга </SPAN>. Это кажущееся упущение замыкающие символы /> означают, что тэг одновременно является и открывающим, и закрывающим.

Примечание Даже если явным образом поместить текст между тэгами <SPAN id = "Message" runat-server> u </SPAN>, он не отобразится браузером, так как команда Message.InnerHtml=s заменит его содержимым переменной s. Если заменить эту команду на Message.InnerHtml- Message.InnerHtml + s, текст в HTML-коде между тэгами <SPAN> и </SPAN> ото-


84

Глава 4

бразится в браузере, а за ним будет расположен текст, созданный в цикле. Пример на Visual Basic .NET В листинге 4-3 показана та же страница, но на Visual Basic .NET, Этот код мало чем отличается от листинга 4-2. <Х@ Page Language="VB" *> <HTML> <HEAD> <TITLE> My First ASPX Page </TITLE> </HEAD> <BODY> <CENTER> Dim tLoop as Integer Dim s as String s=""

For tLoop=1 to 5 s= s + String. Format( "<FONT SIZE={0}>Hello ASP. NET World</FONTXBR>". _ tLoop} Next Message. InnerHtml=s X>

<SPAN id="Message" runat=server /> </CENTER> </BODY> </HTML> Листинг 4-3. Файл SayHelloASPDOTNET.aspx — пример исходного текста приложения На рис. 4-1 показана страница, созданная на Visual Basic .NET. Внешний вид страниц, созданных на Visual Basic .NET и С#, почти не отличается.


Основы разработки приложений в ASP.NET 3 My First flSPX 1'aqe - Microsoft Intern File

Edit

«en

Fsvwtes

Tods

Help

33 г* Go ftllo ДЕР .НЕТ WcrM Hello ASP .НЕТ World

НеЁо ASP.NET World Hello ASP.NET World

Hello ASP.NET World

Рис. 4-1. Вид ASP.NET-страницы (листинг 4-3), созданной на Visual Basic .NET В этом примере я внес несколько очевидных синтаксических изменений. В частности, удалил точки с запятыми в конце операторов и фигурные скобки ({ и }), изменил синтаксис цикла for и объявлений переменных. Хотя я обычно использую переменную loop в C/C++ (а теперь и в С#), в Visual Basic .NET это слово зарезервировано, поэтому я изменил имя переменной.

Методика разработки приложений в ASP.NET В целом, создание приложений в ASP.NET похоже на разработку в более ранних версиях ASP. На рис. 4-2 показана общая схема рабочего процесса с точки зрения ASP-разработчика. Процесс заключается в многократном повторении цикла «редактирование страницы — тестирование».


Глава 4

Редактирование исходного текста

Тестирование

Рис. 4-2. Цикл разработки ASP-страниц В ASP.NET процесс выглядит абсолютно так же, за исключением некоторых базовых операций. На рис. 4-3 показан необходимый процесс: «редактирование —компиляция —тестирование». Процесс компиляции незаметен разработчику —если до запуска приложение не было скомпилировано, это происходит автоматически при запуске. Интересно, что с точки зрения разработчика цикл разработки при переходе от ASP к ASP,NET выглядит так же, тогда как внутренние изменения огромны. Помимо новых языков и рабочей среды .NET, важнейшее преимущество ASP.NET-приложений заключается в том, что они более производительны и надежны, так как компиляция помогает обнаружить грубые синтаксические ошибки до начала исполнения программы.


Основы разработки приложений в ASP.NET

«7

Редактирование исходного текста

Компиляция

Рис. 4-3. Цикл разработки приложений в ASP.NET

Создание Web-приложений в Visual Studio .NET Хотл Web-приложения в ASP.NET -- это лишь один из многих возможных типов приложений, именно их создают чаще всего. Для создания Web-приложений в ASP.NET не обязательно использовать Visual Studio .NET, но эта среда сильно облегчает жизнь. При запуске Visual Studio .NET отображается начальная страница Start Page. Она является входом в среду Visual Studio .NET и позволяет быстро выполнять множество стандартных операций. Одна из замечательных особенностей страницы Start Page —окно My Profile (рис. 4-4). Раньше многие программисты, использующие Visual Studio .NET, предпочитали одну из трех интегрированных сред разработки (integrated development environment, IDE): Visual InterDev, Visual Basic или Visual C + + . Исторически сложилось, что эти среды довольно сильно различаются, и разработчики, пользующиеся


88

Глава 4

одной IDE, часто скептически относились к другим. Чтобы все программисты чувствовали себя одинаково комфортно в новой IDE-среде, в Visual Studio .NET предусмотрена возможность настраивать внешний вид различных окон и схемы клавиатуры в соответствии с личными предпочтениями. Конечно, не всех обрадуют все особенности новой IDE-среды, но общая среда разработки — это необходимый и логичный шаг к поддержке разработки на многих языках.

tfsnfv that the following Sittings Profile: p^isja: Studio o*>* o'iier~~"~ Keyboa-iiScJiii-ne:

[[Default V e t t m g T j 3

Mindow Lav^t.

j Visual Studio Default

jj

I. -* i -

li] Help 1 :: utmns. Pnii J^, QI.L Fts LT SamplH

Microsoft Devclopmrrp,,.

<fe

9.4l»fl

Рис. 4-4. Окно My Profile в Visual Studio .NET

Примечание Хотя Visual Studio .NET поддерживает создание приложений на Visual Basic .NET, C# или C + + , в настоящий момент она не позволяет создавать ASP. NET-приложения, страницы которых написаны на разных языках, например одни — на Visual Basic .NET, а другие — на С#. Я надеюсь, что в будущих версиях среды будет предусмотрена более полная интеграция различных языков в рамках одного решения (solution). Стоит отметить, что so-


Основы разработки приложений в ASP.NET

»9

lution —это термин Visual Studio .NET, который применяется для описания контейнера, объединяющего несколько взаимосвязанных проектов. Изменение внешнего вида окна для имитации различных ЮЕсред в Visual Studio .NET — интересное занятие, которое может вызвать состояние дежа-вю. Хотя мне очень нравился внешний вид Visual C++ 6.0, при создании примеров для этой книги я использовал принятый по умолчанию вид Visual Studio. После нескольких месяцев работы в нем я понял, что он вполне хорош. В Visual Studio .NET предусмотрено несколько способов создания нового проекта. Стандартный — выбрать команду Project в подменю New меню File. Откроется диалоговое окно, показанное на рис. 4-5.

LJ LJ £3 j Л , j

Visual C# Projects Visual C++Projects Setup and Deployment Projects Other Projects Visual Studio Solutions

Windows uppli cation

Class Library

Windows Control Library

«5P.NETW& ASP.NET Web Web Control implication A aroject for (rsarjng an applitston uwh a Web user .nterface chapO

' ...... "•'•

Prefect will be created et http;^lo «More

Рис. 4-5.

1

Диалоговое окно New Project в Visual Studio .NET

В дереве папок слева выбирают язык проекта или один из нескольких типов особых проектов. В версии Enterprise среды Visual Studio .NET в папке Other Projects отображаются шаблоны корпоративных проектов, которые применяются для создания больших распределенных приложений. В зависимости от выбранного типа проекта текст в поле Location диалогового окна New Project изменяется на имя папки или URL-


Глава 4

адрес локального Web-сервера. На рис. 4-5 это виртуальная папка з корне текущей Web-папки, так как тип выбранного проекта — ASP.NET Web Application.

Visual Studio .NET и US-сервер Для примера я предпочел Visual Basic ASP.NET Web Application и назвал проект chap04. По щелчку кнопки OK происходит несколько вещей. Так же как в Visual C + + 6.0, название проекта становится именем папки, в которой хранится приложение. Кроме того, Visual Studio .NET запрашивает Web-сервер (вданном примере локальный Web-сервер) и создает папку для приложения с таким же именем. Когда проект создан, в консоли Internet Information Services отображается папка, созданная для приложения (рис 4-6). У .-? [?i С,-

I Global.asai 3 Global, as ax. vb

AsieniblvInFO'Vb

i

Рис. 4-6. Папка для Web-при ложен ия, создаваемая Visual Studio .NET при создании приложения в ASP.NET В правой панели отображаются значки файлов, созданных Visual Studio .NET. Наиболее важные файлы для вас, как разработчика, — файл Web-формы (предусмотрительно названный WebForml.aspx) и файл программной логики страницы, или CodeBehind-файл, (WebForml.aspx.vb). Если проект создается на С#, файл получит название WebForm! .aspx.cs. Файл Web.config позволяет настраивать параметры приложения (об этом — чуть позже). Кроме того, Visual Studio .NET создает папку bin, в которой хранится скомпилированный код приложения,


Основы разработки приложений в ASP.NET В свойствах папки приложения [правой кнопкой мыши щелкните папку chap04 в консоли Internet Information Services и в контекстном меню выберите команду Properties (Сзойства)З вы не найдете ничего необычного. Чтобы открыть диалоговое окно конфигурирования приложения Application Configuration (Настройка приложения), на вкладке Directory (Каталог) страницы свойств щелкните кнопку Configuration (Натройка) (рис. 4-7).

Рис. 4-7. Диалоговое окно Application Configuration созданной для приложения папки chap04 На вкладке Арр Mappings (Отображение приложений) показан список исполняемых файлов или DLL-библиотек, которые обрабатывают данные расширения. В данном случае полный путь к исполняемому файлу очень длинный, поэтому его не видно целиком — просто поверьте мне на слово, что все исполняемые файлы ASP.NET в IIS указывают на C:\WlNNT\MicrosoftNET\Framework\v1.0.2941\aspnetjsapi.dll. При написании данной главы я использовал версию 1.0.2941 среды .МЕТ — ее номер и показан в имени пути к DLL, которая обрабатывает приложения ASP.NET. Номер версии в пути к файлу очень важен, так как позволяет использовать различные ASP.NET-приложения, работающие с разными версиями ASP.NET.


92

Глава 4

Первая Web-страница, созданная в Visual Studio .NET После создания файлов проекта и папки приложения в IIS окно Visual Studio .NET выглядит, как показано на рис. 4-8. Следует отметить несколько важных моментов, касающихся Visual Studio. Во-первых, обратите сжимание на бледную сетку на вкладке WebForml .aspx — она отображается при включенном режиме Grid Layout и позволяет точно позиционировать компоненты, так же как в традиционной форме в Visual Basic.

Рис. 4-8. Окно Visual Studio после создания нового проекта Web Application в режиме Grid Layout

Примечание Метод обеспечения точного размещения компонентов заслуживает небольшого примечания. Традиционно HTML не позволяет точно управлять расположением компонентов на Webстранице. В режиме Grid Layout для позиционирования компонента применяются DHTML и CSS-таблицы {Cascading Style Sheets), а браузеру сообщают-


Основы разработки приложений в A5P.NET

93

ся точные координаты для его отображения. Это замечательная идея, но ее умаляют две проблемы. Первая -- что делать с устаревшими браузерами, которые не поддерживают DHTML и CSS? Для более-менее точного расположения элементов браузеру отправляется сложная система таблиц, которая в большинстве случаев достаточно хорошо справляется с размещением компонентов. Вторая трудность в том, что при использовании точного управления страницей подчас получается очень неустойчивый внешний вид страницы. Например, если установленные на компьютере шрифты не совсем совпадают с использованными разработчиком, внешний вид может сильно измениться. Решение об использовании режима Flow Layout вместо Grid Layout принимает сам разработчик. Данный параметр располагается в диалоговом окне свойств страницы. Если вы разрабатываете приложение для Интернета, а не для интрасети, в которой конфигурация клиентов обычно определена заранее, не стоит пользоваться режимом Grid Layout, хотя он действительно удобен. В примерах данной книги для выравнивания компонентов используются таблицы, а не режим Grid Layout. Следующий пример — исключение, так как мне хотелось продемонстрировать вам IDE-среду, а режим Grid Layout как нельзя лучше подходит для этого. Чтобы открыть панель Toolbox, щелкните вкладку Toolbox в левой части экрана (снизу от значка Server Explorer) или кнопку Toolbox на панели инструментов. В данном примере я создал две надписи, разместил их на сетке одну под другой и отцентрировал по горизонтали (рис. 4-9). Нижняя надпись немного шире, чем верхняя.


94

Глава 4

«•'

ШМяепск - И Asswiblylnfo.-t : Г] йкфем.уяйсо ; • т ЗоЫ1.аи< • и Sims.Hs

-- a*«b.(orfi0

Рис. 4-9. Основная форма приложения chap04 с двумя надписями Сущесвует два основных способа изменения объектов на ASP.NETстранице в режиме конструирования. В первом применяется панель Properties, которая по умолчанию располагается з нижнем правом углу Visual Studio. Чтобы изменить верхнюю надпись, просто щелкните ее (она называется Label!) и измените ее свойства. В свойстве Text введите «Your First ASP.NET Page» («Ваша первая страница ASP.NET»), Чтобы текст поместился на одну строку, может потребоваться изменить размер надписи. Далее выберите свойство Font. Рядом с ним стоит значок «+», щелкнув который вы откроете список подчиненных свойств. Установите подчиненное свойство Bold в True. Все изменения немедленно отобразятся в окне конструктора. Второй способ изменения объектов —отредактировать исходный текст. Воспользуемся им для изменения второй надписи —Label2. Это можно сделать несколькими способами. Во-первых, обратите внимание на две вкладки в нижней части окна дизайнера Design (она активна) и HTML. Перейдите на вкладку HTML — вы


Основы разработки приложении в ASP.NET увидите исходный HTML-текст, отформатированный способом, который очень напоминает отображение в Visual (nterDev 6.0 (рис 4-10).

Рис. 4-10.

Вид исходного HTML-текста в Visual Studio .NET

Хота этого не видно на рисунке, в самом конце строки с тэгом Label!, между открывающим и закрывающим тэгами asp:Label, расположен текст, который вы ввели в панели Properties. В соответствии с изменениями, сделанными в окне Properties, атрибут Font-Bold установлен в True. Конструктор поддерживает моментальную синхронизацию между представлениями, то есть изменения в исходном HTML-тексте немедленно отображаются на зкладке Design. Например, если щелкнуть открывающий тэг <BODY>, панель Properties изменится и отобразит атрибуты этого тэга. Найдите в списке атрибут bgcolor, щелкните в его поле и либо непосредственно введите нужный цвет, либо щелкните кнопку с многоточием и в диалоговом окне Color Picker выберите цвет. Я выбрал бледно-желтый, или #ffffcc. Соответствующая пара «атрибут— значение» помещается в тэг <BODY>. Если переключиться обратно в режим Design, цвет фона изменится на указанный.


96

Глава 4

Корректировать текст в режиме Design весьма удобно, но нередко приходится менять свойства во время работы приложения. Чтобы увидеть VB-код данной страницы, в меню View выберите команду Code или просто нажмите кнопку F7 — в активной панели отобразится файл Webforml .aspx.vb — исходный текст на Visual Basic .NET. Программа невелика, и часть ее по умолчанию скрыта. Пока не обращайте внимание на эту часть — нас интересует метод Page_Load (немного переформатированный для ясности): Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load 'Put user code to initialize the page here End Sub Я не просто добавил статический текст в Label2, а также текущую дату и время, которые изменяются при каждом обновлении страницы. Сразу после комментария, созданного мастером (Put user code to initialize the page here — «Разместите здесь пользовательский код инициализации страницы»), я добавил строку инициализации страницы: Label2.Text = "The current date and time is " + Now() Данная строка очень похожа на строку Visual Basic, кроме того, понятно, что она делает. Обратите внимание, что для объединения строк я использую знак «плюс» (+), а не амперсанд (&). В отличие от Visual Basic .NET и С# в предыдущей версии Visual Basic нельзя было использовать знак «плюс». В этой книге для создания строки я всегда буду пользоваться этим знаком. Сделав все необходимые изменения, я перехожу в меню Debug и выбираю команду Start, которая запускает приложение с отладчиком. Если изменения внесены после последнего запуска приложения, соответствующие элементы перекомпилируются, поэтому на первый запуск приложения уходит больше времени, чем на последующие. Если приложение скомпилировано без ошибок, открывается страница, показанная на рис. 4-11, Это очень простое приложение, но я надеюсь, что вы получили определенное представление о возможностях IDE Visual Studio .NET. Хотя некоторые особенности разработки доступны также и в других средах, в частности в Visual InterDev 6.0, в Visual Studio .NET они реализованы намного лучше. В Visual InterDev 6.0 я практи-


Основы разработки приложений в ASP.NET чески никогда не пользовался режимом конструирования, так как у него есть очень неприятная особенность — он полностью переформатирует мой аккуратно отформатированный HTML-текст. В Visual Studio .NET форматирование текста при переключении из дизайнера в редактор выполняется более «интеллектуально», а некоторые особые параметры позволяют управлять большинством изменений HTML-кода, вносимых средой Visual Studio .NET.

Your First ASP .NIT Page

The current date and tune is 8/15/2001 П 24:34 AM

I

Рис. 4-11. Страница chap04 после внесения изменений Раньше не было некоторых серверных компонентов, например Label. Компоненты-надписи (label) — это лишь верхушка айсберга всего множества серверных элементов управления. В следующих главах я вернусь к разработке Web-форм в ASP.NET и использованию серверных элементов управления, и даже покажу вам, как создавать пользовательские серверные элементы управления.

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


98

Глава 4

Кроме ASP-подобных приложений, с которыми вы уже познакомились, ASP.NET позволяет разрабатывать еще два вида масштабируемых приложений: XML Web-сервисы и приложения, использующие среду исполнения HTTP, HTTP-обработчики и HTTPмодули.

XML Web-сервисы Насколько часто вам приходилось использовать особо удачный код для обработки данных в другом приложении в корпоративной интрасети или в Интернете? Допустим, есть небольшая программа для нестандартной проверки каких-либо данных, например авторизации кредитной карточки. Получая номер кредитной карточки, функция возвращает информацию о допустимости (или нет) использования карточки. Функция может непосредственно взаимодействовать с базой данных или даже с некоторым сервисом, не имеющим удобного программного интерфейса. Существует множество способов обеспечить доступ к таким сервисам из нескольких приложений. Первый способ — создать служебное приложение, взаимодействующее с потребителями функции по особому протоколу на основе TCP/IP. Он не так уж плох, но придется возвести «вавилонскую башню» из соглашений в виде интерфейсов и протоколов. В каком регистре, верхнем (YES или NO) или нижнем (yes или по), ожидает система подтверждения подлинности кредитной карточки? Какие символы применяются для разделения результата проверки и кода авторизации — запятые или тильды (~)? Через какой порт передаются данные? Обеспечивает ли система пересылку через брандмауэры? Сможет ли кто-нибудь вспомнить все эти подробности через годик-другой? Второй способ — создать Web-страницу, которая принимает аргументы в составе URL-адреса, выполняет проверку и возвращает результат в виде другой страницы, которую получает запросившее проверку приложение, а не браузер. Здесь решается проблема доступности функции через брандмауэр, но остается открытым вопрос о нестандартном программном интерфейсе, который потом легко забудется. Подлинное решение — XML Web-сервисы. Если коротко, это программные компоненты, которые предоставляют приложениям


Основы разработки приложений в ASP.NET

99

услуги через Web, а для обмена сообщениями используют язык XML (Extensible Markup Language). (Более подробно об XML Webсервисах я расскажу в главе 10.) XML Web-сервисы не зависят от среды .NET. По сути, им не очень-то нужна Windows на сервере, и они создаются любыми средствами разработки приложений, поддерживающих протокол SOAP (Simple Object Access Protocol). (В MSDN есть интересная статья «Develop a Web Service: Up and Running with the SOAP Toolkit for Visual Studio», в которой описывается создание XML Web-сервиса с помощью SOAP Toolkit и Visual Studio 6.0. Вы найдете ее на странице http://msdn.microsoft.com/library/periodic/periodOO/webservice.htm.) Так в чем же причина такого шума вокруг XML Web-сервисов в ASP.NET? В простоте их создания, которую обеспечивает среда ASP.NET.

Примечание XML Web-сервисы произведут резолюцию способов доступа к сервисам в Web. Например, на момент написания этих строк корпорация Microsoft и компания eBay объявили о заключении соглашения, которое предусматривает использовать XML Web-сервисы для интеграции сервисов Microsoft, таких, как Carpoint, bCentral и WebTV, с электроной торговой площадкой eBay. Подобный альянс вряд ли стал бы возможен без XML Web-сервисов. Насколько просто создавать XML Web-сервисы в .NET? В листинге 4-4 показан исходный текст XML Web-сервиса на Visual Basic .NET, зтом числе код, генерируемый Visual Studio .NET. Генерируемый IDE-средой код обрамлен тэгами #Region и #End Region. Imports System.Web.Services Public Class Service"! Inherits System.Web.Services.WebService ion " Web Services Designer Generated Code " Public Sub New() MyBase.New() ' Этот вызов необходим для Web Services Designer.


100

Глава 4

InitializeComponent{) 1

Добавьте свой код инициализации после вызова ' InitializeComponertO End Sub 1

Необходим для Web Services Designer Private components As System.ComponentModel.Container ' ПРИМЕЧАНИЕ: Следующая процедура необходима ' для Web Services Designer ' Ее изменяют средствами Web Services Designer. ' He изменяйте его средствами редактора кода. <System. Diagnostics. DfibuggerStepThrough()> _ Private Sub InitializeComponent() components - New System.ComponentModel.Container() End Sub Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) 1 CODEGEN: Эта процедура необходима для Web Services Designer ' He изменяйте его средствами редактора кода. End Sub »End Region ' ПРИМЕР WEB-СЕРВИСА '1 Сервис HelloWorldO возвращает строку Hello World. Чтобы скомпоновать приложение, 1 снимите значки комментария со следующих строк, 1 а затем сохраните и скомпилируйте проект. ' Перед тестированием Web-сервиса, позаботьтесь, ' чтобы файл .asmx бып начальной страницей и нажмите F5. <WebMethod(}> Public Function HelloWorldO As String HelloWorld = "Hello WorldEnd Function End Class Листинг 4-4. Исходный текст простого XML Web-сервиса «Hello World» Ha puc. 4-12 показан результат вызова XML Web-cepsuca —XMLсообщение, которое представлено в виде строки, возвращенной методом HelloWortd класса Service!. Конечно, более интересно,


Основы разработки приложений в ASP.NET

101

когда сервис получает параметры, обрабатывает их и возвращает результат. 'atiltp^'/localhost/WebSeryket/bervkeMsniK/H

bttp:^ocalhort/W*b5errtceiyservicel.asmx/Heto World? ^ixml version="l.Q" encoding="utf-S" ?> <stnng xrri!n$="http://tempiiri.Qrg/">Hello Worlt)</stnng?

Рис. 4-12. Результат вызова Web-сервиса XML «Hello World» (листинг 4-4) XML Web-сервисы открывают новый мир для разработчиков приложений и позволяют даже самым мелким фирмам, разрабатывающим программное обеспечение, предоставлять сервисы в Web, что открывает новые возможности для бизнеса.

HTTP-обработчики и HTTP-модули HTTP-обработчики и HTTP-модули —еще два вида приложений ASP.NET. Они чем-то похожи соответственно на ISAPI-расширения и ISAPI-фильтры. Есть две причины, по которым в ASP обращаются к расширениям или фильтрам ISAPI: • достигнут потолок производительности или масштабируемости; • требуется гибкость, которую обеспечивают только расширения или фильтры ISAPI. Для разрабатываемых сегодня приложений ASP.NET первое условие не так важно. Поскольку приложения ASP. NET работают в одной среде исполнения с HTTP-обработчиками и HTTP-модулями, сложности с производительностью и масштабируемостью возникать не должны. Второе условие гораздо более важно, и вряд ли со временем его важность уменьшится. HTTP-обработчики необходимы, к приме-


Глава 4

102

ру, когда требуется перенести существующее CGl-приложение в ASP. NET или выполнить другие необычные действия, например возвратить бинарные данные. HTTP-модули могут работать как бинарный эквивалент файла Global.asax, отслеживая различные события и предоставляй разработчику приложения максимально гибкие возможности.

Конфигурирование приложений В исходном тексте на Visual Basic .NET в листинге 4-3 отсутствует один элемент. Хотя я всегда хвалю режим Option Explicit, здесь я его не использовал. При желании это можно сделать, указав Explicit="true" в директиве @ Page. Тем не менее эта страница требует объявления переменных — при преобразовании приложения из С# в Visual Basic .NET я пропустил одно упоминание переменной loop — Visual Basic .NET не замедлила с сообщением об ошибке (рис 4-13).

v*o afS*ytHdlo*S>DOlNE ГВауНе laASDO Гглг'. Э. з sp.

Server Error in '/' Application. Compilation Error iGHowna specific enw «tails end uvu^y you

Lin; 17: Line 16!

L-пг i»:

! ins request Please

f- s t Sti-ng.FcrmrtC _ "tFONT S!ZE={0}>HCllo «SF.ЧЕ" Wjrldt/FDNTj 1ЮС)

5ourcr Fili;chMc^\wwwrBora«yHetoASPDOTIeTlSavHHBASPDOTNfTve.aso< Line: 19

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


Основы разработки приложений в ASP.NET

103

только на компьютерах, выполняющих данное приложение. Такое поведение при ошибке не очень правильно, так как G исходном тексте могут содержаться имена пользователей и пароли доступа к базе данных или другие важные данные. Но если атрибут Explicit не установлен в true, почему же появляется сообщение о необъявленной переменной [не вполне правильно обозначенной в данном примере как «Expected an expression» («Ожидается выражение»)]? Причина кроется в файле Web.config. Он не только задает атрибуты Explicit и Strict на каждой странице, но позволяет глобально, на уровне всего приложения, конфигурировать эти и другие параметры. В листинге 4-5 показано содержимое файла Web.config. <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.web> Compilation debug="true" defaultLanguage="C#" explicit="true" batch="true" batchTimeout="30" strict="true" > </compilation> </system.web> </configuration> Листинг 4-5. Простой файл Web.config, в котором атрибуты explicit и strict установлены в true, а значит, их не нужно задавать на каждой странице Visual Basic .NET Если вы знакомы с языком XML, то увидите, что листинг 4-5 простой, правильный (well-formed) XML-документ. В главе 8 я подробнее расскажу об XML, но сейчас вам достаточно знать следующее: •

в отличие от HTML, где разрешается опускать многие закрывающие тэги, в XML обязательно наличие пар открывающих и закрывающих элементов. В некоторых тэгах открывающий и закрывающий элементы можно представлять в виде одного тэга, например: •(compilation debug="true" />


104

Глава 4

• XML чувствителен к регистру. Так, следующая пара тэгов не зерна, так как тэг </Compi!ation> не считается закрывающим для тэга <compilation >: <compilatipn debug='true"> </Compilation> Хотя s ранних бета-еерсиях среды .NET разрешалось игнорировать регистры в атрибутах (например, true и True считались одинаковыми), начиная с версии Beta 2, файлы конфигурации чувствительны к регистру; • значения атрибутов в XML заключаются з кавычки. Такой тэг неверен: Compilation debug=true /> В среде .NET рекомендуется использовать язык XML, но на момент написания этой книги для редактирования файла Web.config не предусмотрено никаких специальных программных административных средств. Это не такая сложная проблема, так как формат XML легок для понимания, но вам придется вручную отлаживать файлы в обычном редакторе, например в Notepad (высокопарно именуемом Notepad .NET первыми разработчиками в ASP.NET). Я не рассматриваю все возможные параметры конфигурации — они описаны в документации на .NET Framework Software Development Kit (SDK), но расскажу о самых важных из них и их роли, а также объясню назначение каждой из частей файла Web.config.

Где хранится файл Web.config Одну из трудностей для ASP-программистов представляет странный набор файлов, которым обрастает любой сложный Web-узел. В ASP единственным файлом конфигурации был Global.asa, и, по сути, он близок файлу Global.asax в ASP.NET. Начинающие ASPразработчики часто спрашивают, где размещать файл Global.asa. На практике оказывается, что его необходимо помещать почти в каждую папку. В ASP.NET файл Web.config позволяет многим узлам сократить число конфигурационных параметров в рамках каждого виртуального узла. Есть корневой файл конфигурации Machine.config, формат которого совпадает с форматом файла Web.config. Он является частью рабочей среды .NET и содержит стандартные


Основы разработки приложений в ASP.NET

105

значения многих параметров. Файл расположен внутри корневой папки Windows, в папке ^vv/nt//r^Microsoft.NET\Framework\<wo/wep_eepcut/>\CONFIG. Все остальные папки узла наследуют параметры у этого корневого файла и файлов Web.config, располагающихся выше в логической иерархии файлов. Например, appSettings — один из возможных разделов файла Web.config. Обычно этот раздел применяется для поддержки переменных, доступных для всех страниц в пределах одного приложения, в нескольких приложениях (если переменная существует в виртуальной папке, а другие приложения располагаются логически ниже ее) или даже во всех приложениях на данном компьютере (если раздел appSettings размещается в файле Machine.config), Отдельные значения из раздела appSettings иногда переопределяются в зависимости от места в иерархии файла Web.config. Допустим, файл Machine.config содержит следующий раздел (помещенный между тэгами <configuration> и </сопfiguration>); <appSettings> <add key="dsn" value="myDSN" /> </appSettings> Кроме того, есть виртуальная папка Test, в конфигурационном файле которой между тэгами <сопfiguration> и </configuration > содержится следующий раздел: <appSettings> <add key="dsn" value="myl_ocalDSN" /> </appSettings>

Если этот раздел appSettings — единственный на компьютере, каждая страница, которая получает из этого раздела ключ «dsn», получает значение «myDSN», кроме страниц приложения Test, Страницы, входящие в приложение Test или в папки, расположенные логически ниже папки Test, получат значение «myLocalDSN»,

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


106

Глава 4

носиться более лояльно к подобным ошибкам, но в текущей версии «защиты от дурака» не предусмотрено. Кроме того, начиная с версии Beta 2, все файлы Web.config чувствительны к регистру. Такое ограничение представляется разумным, если учесть, что правильные (well-formed) XML-файлы чувствительны к регистру, ведь Web.config — это и есть правильные XML-файлы. Тем не менее чувствительность к регистру станет причиной не одного приступа головной боли. Никогда не пытайтесь это делать сами! Допустим, ресурс физически расположен по адресу c:\Subdirl\Subdir2\Resource.aspx. Виртуальный каталог VirtualDirectory! указывает на c:\SubDir1, a VirtualDirectory2 — на c:\Subdir1\Subdtr2. Если открыть файл Resource.aspx, расположенный по адресу http://localho$t/Virtua!Directory1/Subdir2/ Resource.aspx, его параметры будут сильно отличаться от параметров файла, расположенного по адресу http://localhost/VirtualDirectory2/Re5ource.aspx. Это происходит, потому что наследование информации о конфигурации от файла Web.config основано не на физической иерархии папки, а на логической, определяемой виртуальной структурой. Очевидно, чтобы при каждом доступе к ресурсу использовался один набор конфигурационных параметров, важно избегать такой конфигурации. Файлы конфигурации состоят из большого числа разделов. Далее я опишу возможные разделы в алфавитном порядке, а некоторые даже снабжу примерами.

Раздел authentication Среда ASP.NET поддерживает несколько способов аутентификации пользователя. В качестве примера приведу раздел authentication файла Web.config, настроенного для аутентификации на основе форм: Authentication mode="^orms"> <forms name=".ASPXUSEROEMO" loginUrl="login.aspx"


Основы разработки приложений в ASP.NET

107

protection="All" timeout="60" /> </authentication> Значения атрибута mode тэга <authentication> показаны з таблице 4-2. Таблица 4-2. Значения атрибута mode Значение

Описание

Forms

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

Windows

Для проверки подлинности пользователя применяется механизм аутентификации Windows

Passport

Применяется аутентификация на основе Microsoft Passport

None

Аутентификация не выполняется

Аутентификация Windows в среде ASP.NET и з ранних версиях ASP очень похожи. В этом методе применяется встроенная способность IIS выполнять аутентификацию пользователей Windows. В этом случае аутентификация Windows дополняет авторизацию пользователя и его роли, но об этом — з следующем разделе. Для аутентификации на основе Microsoft Passport необходима внешняя база данных пользователей. На компьютерах, поддерживающих этот вид аутентификации, необходимо установить Passport SDK. Среда ASP.NET обеспечивает упакозку Passport SDK. Аутентификация на основе форм распространена з Интернетприложениях, когда велика вероятность, что не все пользователи являются членами домена Windows. Хотя такая проверка подлинности может выполняться по традиционной технологии ASP, ASP.NET заметно облегчает эту проверку, создавая формальный каркас поддержки аутентификации.

Отличия ASP.NET В ASP.NET стандартный метод аутентификации на основе форм заключается в использовании обработчика событий Session_On$tart, расположенного в файле Global.asa и выполняющего переадресацию из новых сеансов на страницу входа в систему. Этот метод плохо масштабируется, так как в ASP состояние сеанса не поддерживается в рамках фермы Web-серверов. Метод аутен-


108

Глава 4

тификации на основе форм обеспечивает более четкий механизм проверки пользователя при входе в систему. Примечание Большинство атрибутов во всех файлах конфигурации задаются по правилу роста к концу слова (camel casing), что означает использование строчной первой буквы имени атрибута и заглавных первых букв присоединенных к нему слов, например loginUrl. Данное соглашение отличается от использованных в ранних бета-версиях, где тот же атрибут можно задавать в стиле Pascal, например Login Url. При аутентификации на основе форм можно использовать подтэг <forms>. Его атрибуты перечислены в таблице 4-3. Таблица 4-3. Атрибут

Атрибуты тэга < forms > Описание

loginUrl

URL-адрес, на который переадресуются пользователи, не прошедшие аутентификацию. Данный URL может располагаться на том же или другом компьютере, но в последнем случае атрибут decryptionKey должен совпадать на обоих компьютерах. decryptionKey —это атрибут тэга <machineKey> в файле Machine.con fig

Name

Имя cookie-файла, который используется для аутентификации. Если аутентификацию на основе форм выполняют несколько приложений на одном компьютере, то имя этого файла должно отличаться в разных приложениях, В ASP.NET в качестве пути к cookie-файлу применяется слэш (/) Задает время в минутах до окончания срока действия cookie-файла. Он обновляется по истечении половины сроки действия — таким образом, сокращается число предупреждений пользователя о получении cookie, если включен режим уведомления о размещении cookie-файла. Так как cookie-файл может обновляться, текущее значение тайм-аута иногда неточно. Значит, нельзя полностью полагаться на то, что cookieфайл перестанет действовать ровно через интервал времени в секундах, заданный в атрибуте timeout. По умолчанию значение атрибута —30

Timeout


Основы разработки приложений в ASP.NET

109

Таблица 4-3. (продолжение) Атрибут

Описание

Path

Путь к cookie-файлу. По умолчанию здесь установлен слэш (/). Этот атрибут можно изменять, задавая значение тэга <forms> или используя программные средства

Protection

Тип защиты cookie-файла. Разрешенные значения: Validation, Encryption, None и All. При значении Validation данные cookie-файла проверяются, но не шифруются. При значении Encryption данные шифруются, но не проверяются. При значении None не выполняется ни то, ни другое. При значении АН (по умолчанию) данные шифруются и проверяются на предмет изменений, возникших во время пересылки. Для всех, кроме самых маловажных данных, имеет смысл использовать значение по умолчанию, пожертвовав небольшим снижением производительности

Примечание Зачем проверять правильность cookieфайлов? Поскольку иногда они позволяют получить доступ к информации, не предназначенной для общего доступа, проверка правильности данных cookieфайла и отказ в доступе в случае неверных значений не позволит, к примеру, «захватить» чужую виртуальную корзину с покупками. Простой пример аутентификации на основе форм показан в листингах 4-6, 4-7 и 4-8. В этом несложном примере (файл Login.aspx) имя пользователя и пароль «жестко» определены в самом коде (листинг 4-6). Также, здесь вводится новый класс объектов пользовательского интерфейса. В листинге 4-6 отображающаяся на экране кнопка — это не стандартная HTML-кнопка отправки формы, да и вообще — не кнопка, а объект asp:button. В главе 5 вы познакомитесь с этими объектами поближе, а пока просто поверьте мне на слово, что они работают именно так, как ожидается, а также что событие OnClick инициирует исполнение кода метода LoginjClick (он расположен з верхней части страницы). Детали работы метода Login_Click в листинге 4-6 не так важны, но нас интересует вызов метода FormsAuthentication.RedirectFmmLoginPage. Первый передаваемый этому методу параметр — имя пользователя, полученное из свойства UsefEmail.Value, — это происходит


110

Глава 4

пока загадочным для вас образом (более подробно о передаче значений из серверных элементов управления я расскажу в глазе 5). Второй существенно важный параметр (ему присваивается значение false) указывает, что не нужно создавать постоянный cookie-файл. <%@ Import Namespace="System. Web. Security " K> <html> <script language="C«" runat=server> void Login Click(0bject sender, EventArgs E) ( // Аутентификация пользователя: в этом примере вход разрешается // лишь пользователю doug@programmingasp.net // с паролем 'password' if ((UserEmail. Value == "doug@programmingasp, net") && (UserPass. Value == "password")) { FormsAuthentication.RedirectFromLoginPage( UserEmail. Value, false); ! else i Msg.Text = "Invalid Credentials: Please try again"; </script> <body> <form runat=server> <center> <h3> <font face="Verdana" color=blue>Login Page</font> </h3> <table> <tr> <td> Email: </td> <td> <input id="UserEmail" type="text" runat=server size=30 /> </td> <td>


Основы разработки приложений в A5P.NET

111

<ASP:RequiredFieldValidator ControlToValidate="UserEmail" Display="Static" ErrorMessage="*" runat=server /> </td> </tr> <tr> <td> Password: </td> <td> <input id="UserPass" type=password runat=server size=30 /> </td> <td> <ASP:RequiredFieldValidator ControlToValidate="UserPass" Oisplay="Static" ErrorMessage="*" runat=server /> </td> </tr> <tr> <td colspan=3 align="center"> <asp:button text="Login" OnClick="Login_Click" runat=server> </asp:button> <P> <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server /> </td> </tr> </table> </center> </form> </body> </html> Листинг 4-6. Страница входа в систему — пример процедуры аутентификации (файл Login.aspx) В листинге 4-7 показан еще один скучный пример (по крайней мере, он станет скучным, когда вы разберетесь, в чем состоит волшебство форм ASP.NET, но об этом — в главе 5). Эта форма


112

Глава 4

просто идентифицирует пользователя и разрешает ему выйти из системы. <Я@ Import Namespace="System.Web.Security " %> <html> <script language="Cft" runat=server> void Page_Load(Object Src, EventArgs E ) { Welcome.Text = "Hello, " + User.Identity.Name; I void Signout_Click(Object sender, EventArgs E) { FormsAuthentication.SignOut(); Response.Redirect("login.aspx"); </script> <body> <h3>

<font face="Verdana">Using Cookie Authentication</font> </h3> <form runat=server> <h3> <asp:label id="Welcome" runat=server /> </h3> <asp:button text="Signout" OnClick="Signout_Click" runat=server /> </form> </body> </html> Листинг 4-7. Служебная страница в примере аутентификации при выходе из системы (файл Default.aspx) В листинге 4-8 показан файл конфигурации Web.config данного приложения. Это также очень простой файл. Нас интересует раздел authentication, no сути аналогичный показанному ранее тэгу authentication, а также связанный с ним (подробнее об этом я расскажу в следующем разделе) тэг authorization.

Примечание Данный файл Web.config должен находиться в корне каталога Web-приложения в IIS, a сам каталог следует настроить как каталог приложения, а не как виртуальный.


Основы разработки приложении в ASP.NET

113

<configuration> <system.web> <authentication mode="Forms"> <forms name=".ASPXUSERDEMO" loginUrl="login.aspx" protection="All" timeout="60" /> </authentication> <authorization> <deny users="?" /> </authorization> <globalization requestEncoding="UTF-8" responseEncoding="UTF-8" /> </system.web> </configuration> Листинг 4-8. Файл конфигурации в примере аутентификации Есть еще одна тонкость организации аутентификации на основе форм. Между тэгами <authentication> можно разместить раздел credentials, содержащий информацию об именах пользователей и паролях. Например, в раздел authentication файла Web.config (листинг 4-8) добавить следующие строки: <credentials passwordFormat="Clear" > <user name="Mary" password="littlelamb"/> <user name="Jill" password="tiphill"/> </credentials> V тэга <credentials> есть один атрибут — passwordFormat, Его возможные значения перечислены в таблице 4-4. Таблица 4-4. Значения атрибута passwordFormat Значение

Описание

Clear

Сохранение паролей открытым текстом. Этот вариант не обеспечивает никакой защиты, но очень удобен для тестирования Требует применения алгоритма SHA (Secure Hash Algorithm). Пароли сохраняются в виде 160-битных хешей SHA1, в которых устранены проблемы первой версии алгоритма SHA Сохраняет пароли в виде 128-битных хешей MD5. Это значение гораздо надежнее, чем традиционная контрольная сумма

SHA1

MD5

5 Зак. 422


114

Глава 4

При проверке имени пользователя и пароля с применением формы она должна вызывать метод Authenticate класса System.Web. Security.FormsAuthentication.

Раздел authorization После аутентификации пользователя иногда требуется проверить, разрешено ли ему работать с приложением. Для этого в разделе authorization применяются тэги <allow> и <deny>, з которых можно указывать как отдельных пользователей, так и группы, или роли. Как говорилось в предыдущем разделе, аутентификация Windows позволяет сопоставить ролям группы пользователей Windows NT. Тэги <allow> и <deny> просматриваются, пока не находится запись, соответствующая авторизуемому пользователю. Если это тэг <allow>, пользователь получает доступ, з противном случае (обнаружен тэг <deny>) в доступе отказывают. Доступ также закрывается, если не найдено ни одного подходящего правила. Вообще говоря, на сайгах, где важна авторизация, отказ в доступе необходимо «прописать» явно, указав тэг <deny users="*"/>. Раздел customErrors Недостаточная ясность сообщений об ошибках вызывает проблемы у ASP-разработчиков. В среде ASP.NET эти проблемы устранены, и здесь сообщения об ошибках более информативны и нередко содержат не только строку, вызвавшую ошибку, но и несколько предшествующих и следующих за ней строк. Эта дополнительная информация важна, так как часто ошибка кроется в ошибке на предыдущей строке. На рис. 4-14 показан пример сообщения об ошибке в ASP.NET. В нижней части страницы с сообщением об ошибке расположено несколько ссылок, полезных для разработчиков. Первая из них — Show Detailed Compiler Output («показать подробную выходную информацию компилятора»). При щелчке этой ссылки отображаются сообщения, которые выводились бы при использовании компилятора командной строки. Это полезно, если до самой ошибки появлялись сообщения, способные дать ключ к разгадке происходящего. Вторая ссылка — Show Complete Compilation Source («показать весь исходный текст»). По щелчку


Основы разработки приложений в ASP.NET

115

этой ссылки отображается подробный листинг исходного текста, поступивший на вход компилятора при генерации страницы. При переработке исходного текста и получении кода, необходимого ASP.NET для создания страницы, простая страница Login.aspx (листинг 4-6} «распухает» до 400 строк. Понимать этот код не обязательно, но в некоторых случаях это помогает при отладке приложений.

Server Error in '/FormsAuth' Application. Compilation Error Description: An error occurred during the compilation of specific error detois any mMffy yoix sc-^ce coaeaowonia

h

*->у1Шеау,*г- 'ке this e i JC-E: Pltciie i^itwthe foflowmc

CompNer Error Mc**aae:CS1SOl Noaverlaad for ndhoa 'RediredFromLoQinPage'w**^ Ч'я^цгнйз

5hCi« B«»led Compilar Outoul: Shn. CdmrieU? СотоНЯюп Sair,

,r

Рис. 4-14. Сообщение об ошибке в ASP.NET Обратите внимание на то, что на странице с сообщениями об ошибке отображается имя пользователя и пароль, требуемые страницей входа в систему! Конечно, этот пример выдуман и никто не станет использовать такую «систему безопасности» в реальных приложениях, но часто требуется скрыть от пользователей другие участки программы, например содержащие имена пользователей и пароли в строках подключения к базе данных. Встраивание строк подключения прямо в исходный код приложения — это по многим причинам очень неудачное решение, но в любом случае, никогда не помешает закрыть пользователям доступ к исходному тексту.


116

Глава 4

Раздел custom Errors файла Web.config применяется для разрешения показа подобных сообщении об ошибках только разработчикам при создании и тестировании программ, но не пользователям. В таблице 4-5 показаны атрибуты, поддерживаемые тэгом < custom Errors >. Таблица 4-5.

Атрибут

Атрибуты тэга <customErrors>

Значение

ciefaultRedirect mode

( 'л Oft

RemoteOniy

Описание Задает URL-адрес для переадресации пользователя Включает, отключает или показывает пользовательские сообщения об ошибках только на удаленных клиентах Пользовательские сообщения включены Пользовательские сообщения отключены Пользовательские сообщения отображаются только на удаленных клиентах

По умолчанию атрибут mode установлен в RemoteOniy, и страницы с сообщением об ошибке (как на рис. 4-14) удаленные пользователи не видят —вместо них отображаются страницы с пользовательскими сообщениями. Страница, которая по умолчанию показывается удаленным пользователям, в действительности предназначена только разработчикам. В ней поясняется, как просмотреть подробную информацию об ошибке, изменив раздел customErrors файла Web.config. Определение атрибута defaultRedirect позволяет, к примеру, переадресовать пользователей на страницу, которая уведомляет администратора сайта об ошибке. Также используется подчиненный тэг <error> тэга <customErrors>, причем его можно указывать несколько раз. В таблице 4-6 перечислены два атрибута, которые поддерживает подтэг <error>. Таблица 4-6. Атрибуты подтэга <еггог> Атрибут

Описание

statusCode

Задает код ошибки, который переадресует браузер на нестандартную страницу с сообщением об ошибке


Основы разработки приложений в ASP.NET Таблица 4-6.

117

(продолжение)

Атрибут

Описание

redirect

Задает страницу для переадресации при возникновении ошибки, определенной в атрибуте statusCode

Раздел httpHandlers Этот раздел сопоставляет входящие запросы классу IHttpHandler или IHttpHandlerFactory—• в зависимости от запрошенного URLадреса и HTTP-команды.

Примечание Под HTTP командами (HTTP verbs) подразумеваются ключевые слова, определяющие действия Web-сервера. Если вы занимаетесь HTMLразработкой/ вам знакомы HTML-команды POST и GET. В HTML-формах это значения атрибута method тэга <FORM>. Если задано значение СЕТ, все значения полей формы добавляются к URL-адресу, определенному в атрибуте action. А если указано значение POST, данные полей формы отсылаются в теле HTTP-сообщения. Разница между ними в том, что при использовании свойства СЕТ все элементы формы, отсылаемые в составе URL-адреса, могут содержать информацию, которую «показывать» не следует (например, имя пользователя и пароль). Свойство POST предпочтительнее, но на практике ASP.NET-разработчики обычно перекладывают решение этих вопросов на саму среду. В главе 5 вы узнаете, что в большинстве тэгов форм просто используется пара «атрибут — значение» runat=server. Раздел httpHandlers файла Web.config задает HTTP-обработчики, которые используются в приложениях, и порядок их использования. Данный раздел поддерживает три подтэга — <add>, <remove> и <dear>. Чтобы определить, к какому обработчику попадет поступивший запрос, просматриваются все каталоги более высоких уровней (логические, а не физические) и обрабатываются все подтэги <add> и <remove>. HTTP-модуль, помещенный на более высокий уровень подтэгом <add>, может удалять подтэг <remove> на более низком уровне.


118

Глава 4

Подтэг <add> добавляет HTTP-обработчик. Как видно из таблицы 4-7, он поддерживает три атрибута. Таблица 4-7. Атрибуты подтэга <add> в разделе httpHandlers Атрибут

Описание

verb

Список HTTP-команд, разделенных запятыми, например GET, PUT, POST, или символом «звездочка» (*)

path

URL-путь UAJ просто строка со знаками подстановки, например *.aspx Комбинация «сборка+класо>, Сборка (assembly) — это группа логически объединенных файлов для удобства работы с ними. Сначала рабочая среда .NET проводит поиск в папке bin данного приложения, а затем — в системном кэше сборок

type

Подтэг <remove> удаляет HTTP-обработчик, определенный ранее в подтэге <add>. Команда/путь в подтэге <remove> должны точно соответствовать команде/пути, заданным з предшествующем подтэге <add>. Хотя добавление и удаление НТТР-обработчикоз может показаться глупым, не забывайте, что файлы конфигурации просматриваются от корня к текущей папке (по логическим папкам, а не физическим), поэтому разумно ожидать, что на каком-то этапе обработчик, необходимый на более высоком уровне, не потребуется приложениям, расположенным ниже уровнем. Подтэг <remove> поддерживает два атрибута: verb и pat/?, совпадающие с соответствующими атрибутами подтэга <add>. Последний поддерживаемый подтэг в разделе httpHandlers — <dear>. Если он задан, все наследуемые или настроенные сопоставления HTTP-обработчиков удаляются. Вот простой пример раздела httpHandlers: <httpHandlers> <add verb="*" path="MvApp.New" type="MyApp.New, HyApp" /> <add verb="*" path="MyApp.Baz" type=" MyApp.Baz, MyApp" /> </httpHandlers>

Здесь все HTTP-команды, обращенные к MyApp.New, nepeagpeсуются в класс MyApp.New сборки MyApp, а все HTTP-команды, адресованные MyApp.Baz, передаются классу MyApp.Baz сборки МуАрр.


Основы разработки приложений в ASP.NET

119

Раздел HttpModules Раздел httpModules файла Web.config содержит информацию, подобную содержащейся в описанном ранее разделе httpHandlers. Он также поддерживает три подтэга: <add>, <remove> и <clear>. Как видно из таблицы 4-8, подтэг <add> поддерживает два атрибута: type и пате. Таблица 4-8. Атрибуты подтэга <add> в разделе httpModules Атрибут

Описание

type

Определяет комбинацию «сборка+класо>, в которой элементы разделены запятыми. Среда ASP.NET выполняет поиск в папке bin данного приложения, а затем в системном кэше сборок

пате

Задает имя, используемое приложением для ссылки на модуль, заданный в атрибуте type

Подтэг <remove> работает так же, как его брат-близнец в разделе httpHandlers. Атрибуты type и name используются для выбора добавленных ранее HTTP-модулей. Подтэг <с/еаг> удаляет из приложения все определенные ранее HTTP-модули.

Раздел Identity Раздел identity файла Web.config управляет идентификационными данными Web-приложения. Этот раздел позволяет определить олицетворение (impersonation) — использование идентификатора пользователя с клиентского компьютера для доступа к файлам на сервере. Допустим, что на Web-сервере в интрасети есть два виртуальных каталога: Employees (сотрудники) и Managers (руководители). Если все пользователи работают в Windows и имеют учетные записи домена Windows 2000, а оба каталога расположены на Web-сервере на томе NTFS, то предотвратить доступ рядовых сотрудников к виртуальной папке Managers можно не средствами логики приложения, а применив файловые разрешения NTFS папки Managers. Для этого разрешение на доступ к папке Managers предоставляется только руководителям. Как видно из таблицы 4-9, тэг <identity> поддерживает три атрибута.


120

Глава 4

Атрибуты тэга < identity >

Таблица 4-9. Атрибут

Параметр

Описание Определяет, используется или нет олицетворение клиента при всех запросах

impersonate True

Разрешает олицетворение клиента

False

Запрещает олицетворение клиента (по умолчанию)

userName

Задает имя пользователя, которое применяется, если атрибут impersonate установлен в True

password

Задает пароль, который используется, если атрибут impersonate установлен в True

Раздел pages Раздел pages файла Web.config содержит информацию, присущую только данной странице. Она настраивается на уровне компьютера, сайта, приложения или виртуальной папки. Как видно из таблицы 4-10, тэг <pages> поддерживает шесть атрибутов. Таблица 4-10. Атрибут

Атрибуты тэга < pages >

Значение

buffer

Определяет, используется ли буферизация ответов с определенного URL-адреса true false

enableSessionState

Буферизация ответов включена Буферизация ответов отключена Определяет, поддерживается ли состояние сеанса

true false Readonly enableViewState

Состояние сеанса поддерживается Состояние сеанса не поддерживается Данные о состоянии сеанса доступны для просмотра, но не для записи Определяет, разрешен ли просмотр состояния страницы (состояния ее элементов управления)

true false pageBaseType

Описание

Просмотр разрешен Просмотр отключен Задает CodeBehind-класс, которому наследуют ASPX-страницы


Основы разработки приложений в ASP.NET Таблица 4-10. Атрибут

121

(продолжение,)

Значение

Описание

userControlBaseType

Задает пользовательский элемент управлеHim, которому наследуют другие пользовательские элементы управления

autoEventWireup

Определяет, разрешены ли по умолчанию события страницы true

События страницы автоматически поддерживаются

false

События страницы автоматически не поддерживаются

Примечание Атрибут autoEventWireup в свое время казался неплохой задумкой. События связываются с элементами управления на основании имен методов и компонентов. Если элемент управления называется button!, то его функция-обработчик buttonl_Ciick. Но з действительности этот атрибут по большей части приносит больше хлопот, чем пользы, что не раз обсуждалось в группах новостей пользователей A5P.NET. Он — вечный источник неразберихи. Во всех примерах этой книги я назначаю обработчики событий вручную. Отличия ASP.NET ASP-разработчики привыкли к тому, что при выходе, за рамками определенного сервера состояние ASP-сеанса теряется, и его нельзя сохранить с применением стандартной процедуры определения состояния. Проблема в том, что в ASP состояние сеанса сохраняется на самом Web-сервере. В кластерной среде Web-фермы нельзя сделать так, чтобы запросы от конкретного клиента попадали на один, вполне определенный Web-сервер кластера. Для решения этой проблемы в ASP используется трюк, который заключается в сохранении части состояния сеанса в зашифрованных cookie-файлах, а затем использовании их для доступа к базе данных и получения остальной информации о сеансе. Другой обходной путь — применение идентифи-


122

Глава 4

катора сеанса, который передается от страницы к странице и требуется для доступа к базе данных с информацией о сеансах. В среде ASP.NET не надо использовать обходные пути. Состояние сеанса можно сохранить на сервере или в базе данных SQLсервера. В разделе «Раздел session State» я расскажу об этом поподробнее. Раздел processModel Этот раздел файла Web.config управляет параметрами модели процессов ASP.NET на Web-сервере IIS. Он отличается от рассмотренных ранее тем, что его считывает неуправляемая DLLбиблиотека Aspnetjsapi.dll, а не система конфигурирования управляемого кода. 8 разделе processModel много параметров, влияющих не производительность системы.

Внимание! В разделе processModel настраиваются многие характеристики, в том числе атрибуты, определяющие работу ASP.NET на компьютерах с несколькими процессорами. Например, можно задать маску процессора, определяющую, на каком процессоре исполняется код ASP.NET. Если зам уже захотелось попробовать, то прежде чем приступать к дилетантской возне с процессами, подумайте вот о чем: корпорация Microsoft потратила миллионы долларов и массу времени на создание подсистемы управления процессами Windows 2000, которая эффективно распределяет работу между несколькими процессорами в многопроцессорных системах. Только в чрезвычайно редких, исключительных случаях удастся ускорить работу, вручную «подкручивая» распределение процессорного времени. Тэг <processModel> поддерживает большое количество атрибутов. Наиболее важные из них перечислены в таблице 4-11. В ASP.NET на каждом доступном процессоре исполняется лишь один процесс. В системе с четырьмя процессорами, где всем процессорам разрешено исполнять код ASP.NET (это зависит от атрибутов cpuMask и webGarderi) запускаются четыре процесса.


Основы разработки приложений в ASP.NET

123

Если значение атрибута cpuMask равно 7, запустятся только три процесса (подробности — в таблице 4-11). Таблица 4-11.

Атрибуты тэга < processModel >

Атрибут

Значение

Описание Определяет, включена ли поддержка модели процессов

enable True False

Поддержка модели процессов включена Поддержка модели процессов отключена

timeout

Задает в минутах время до запуска средой ASP.NET нового рабочего процесса, который займет место текущего. По умолчанию значение атрибута — infinite (бесконечность)

idleTimeout

Задает в минутах время простоя рабочего процесса до его остановки средой ASP.NET. По умолчанию значение атрибута •— infinite (бесконечность)

shutdown Timeout

Задает в минутах время, выделенное рабочему процессу для самостоятельного завершения. Если по истечении этого времени рабочий процесс не завершается, его закрывает ASP.NET. Формат времени — час/мин/сек, а значения по умолчанию — 0:00:05

requestiimit

Задает число запросов до того, какASP.NET автоматически заместит текущий рабочий процесс новым. По умолчанию значение атрибута —-infinite (бесконечность)

requestQueuei/mit

Задает число запросов в очереди до того, как ASP.NET запустит новый рабочий процесс и переназначит запросы. Значение по умолчанию —5000

memoryiimit

Задает максимальный объем памяти в процентах от общего объема памяти системы, выделяемый рабочему процессу до того, как ASP.NET запустит новый процесс и переназначит существующие запросы. Значение по умолчанию —40. Указывается только число, знак процента (%) опускается (см. след, стр.)


124

Глава 4

Таблица 4-11.

(продолжение)

Атрибут

Значение

Описание

cpuMask

Задает битовую маску, которая показывает, на каких процессорах в многопроцессорной системе разрешено исполнять процессы ASP. NET. На компьютерах с четырьмя процессорами бинарное значение 0111 (десятичное 7) означает, что процессоры с номерами из диапазона 0 — 2 исполняют процессы ASP.NET, а процессор 3 — нет. Этот атрибут работает совместно с атрибутом webCarden

webGarden

Совместно с атрибутом cpuMask управляет привязкой (affinity) процессоров. Система с несколькими процессорами называется Web-садом (Web garden), возможно, по аналогии с кластером ПК, часто называемым Web-фермой True

False

Определяет, что для управления процессорами следует использовать подсистему Windows (no умолчанию) Определяет, что процессоры, исполняющие код ASP.NET, задаются атрибутом

userName

Задает учетную запись, используемую рабочими процессами. По умолчанию процессы применяют учетную запись US-сервера

password

Задает пароль для учетной записи, определенной в атрибуте userName

logLeve/

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

АИ None Errors


Основы разработки приложений в ASP.NET

125

Таблица 4-11. (продолжение} Атрибут

Значение

clientConnectedCheck

comAuthenticationLevel

comtmpersonationievel

tnaxWorkerThreads

5—100

maxtoThreads

5—1 00

Описание Задает время ожидания запроса в очереди до следующей проверки наличия клиента «на линии», так как часто пользователь не дожидается загрузки страницы и переходит на следующую Задает уровень аутентификации в подсистеме безопасности DCOM. Допустимые значения: Default, None, Connect (по умолчанию), Call, Pkt, Pktlntegrity и PktPrivacy Задает уровень олицетворения в подсистеме безопасности СОМ. Допустимые значения: Default, Anonymous, Identify, Impersonate u Delegate. (В данный момент значение Anonymous не поддерживается.) Задает максимальное количество рабочих потоков в процессе в расчете на один процессор. Значение по умолчанию — 25 Задает максимальное число потоков ввода/вывода процесса в расчете на один процессор. Значение по умолчанию — 25

Раздел sessionState В ASP.NET обеспечивается гораздо более обширная и гибкая поддержка состояния сеансов, чем з ASP. Для разработчиков небольших Web-узлов в Интернете или интрасети возможностей поддержки сеансов, предлагаемой ASP, вполне хватало. Проблема в том, что состояние ASP-сеанса не масштабируется — его нельзя распространить на несколько Web-серверов. В ASP состояние сеанса хранилось на Web-сервере, и поэтому при использовании кластеров, например, на базе службы балансировки сетевой нагрузки (Network Load Balancing) не было никакой гарантии, что запросы определенного клиента будет обслуживать один и тот же Web-сервер кластера. Другое ограничение состояния сеанса в ASP — необходимость использования cookie-файлов. Это требование — не такое уж неустранимое препятствие, так как сегодня практически все браузе-


Глава 4

126

ры поддерживают cookie, и, кроме того, 8 абсолютном большинстве Интернет-сайтов требуется поддержка cookie-файлов, и это заставило всех (ну разве что за исключением отпетых параноиков) смириться с использованием хотя бы временных cookie-файлов. Раздел sessionState файла Web.config управляет поддержкой состояния сеансов. Как видно из таблицы 4-12, тэг <ses$ionSt3te> поддерживает пять атрибутов. Таблица 4-12. Атрибуты тэга < sessionState > Атрибут

Значение

Описание Задает место хранения информации о состоянии сеансов

Mode Off

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

fnproc

Состояние сеансов хранится локально, как в ASP

StateServer

Состояние сеансов хранится на удаленном сервере состояний

SqlServer

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

cookie/ess

Определяет, должны ли применяться клиентские cookie-файлы при сохранении состояния сеанса True

При сохранении состояния сеанса применяются cookie-файлы

False

Пр сохранении состояния сеанса cookie-файлы не применяются

timeout

Задает время в минутах отсутствия активности сеанса, по истечении которого он закрывается. Значение по умолчанию — 20 минут (как в ASP)

stateConnectionString

Задает имя сервера и порта, на котором удаленно хранится состояние сеанса (например, 192.168.1.100:8484). Этот атрибут необходимо задать, если атрибут mode установлен в StateServer

scflConnectionString

Задает строку подключения к SQL-серверу, на котором требуется хранить состояние сеанса (например, data $ource= 192.168.1.100;user id'=sa;password'=). Этот атрибут необходимо задать, если атрибут mode установлен в StateServer


Основы разработки приложений в ASP.NET

127

В ASP.NET применяется то же правило минимизации количества данных, сохраняемых в сеансе, что и в ASP.

Раздел trace В ASP большая сложность для разработчиков заключалась в трудности получения подробной отладочной информации. Что именно происходило на странице, когда возникла ошибка? Какой участок исходного текста исполнялся? В ASP.NET предусмотрен гораздо более совершенный механизм предоставления отладочной информации, а раздел trace файла Web.config позволяет задать параметры службы трассировки. На рис. 4-15 показана страница, исполняемая со включенной трассировкой и атрибутом pageOutput, установленным в true. <ЙИ>Я

FWMteS

In*

Hffi

Л ,l.i

nirias;=

Login Page Email: Password: Persistent Cookie:

TiniBofRequest: Request Encoding:

г

9/12/2001 12:15:43 PM Unicode (UTF-B)

Efid !nit Begin ^reRander End PluRender Begin BaveViewState ' EndSaveViewState Begin Render

Status Cads: Response Encoding:

0.000142 В. С00691 0.1666SS D.237S3E 0.257129 0 257232

200 Lncode (UTF-

О.Л00142 O.OOOE49 0 16Б9Э1 0.070910 O.OI9S97 a. 000104

Рис. 4-15. Результат исполнения файла Login.aspx (листинг 4-6) при включенной трассировке и атрибуте pageOutput, установленном в (rue Как видно из таблицы 4-13, тэг <trace> поддерживает пять атрибутов.


Глава 4

128 Таблица 4-13. Атрибуты тэга < trace > Атрибут

Значение

enabled

Описание Определяет, включена ли трассировка

True False

Трассировка включена Трассировка отключена (по умолчанию)

requestLimit

Указывает, сколько запросов трассировки сохраняется на сервере. Значение по умолчанию —10

pageOutput

Указывает, отображается ли трассировочная информация в конце каждой страницы True

Трассировочная информация добавляется на каждую страницу

False

Трассировочная информация не добавляется (по умолчанию)

traceMode

facalOnfy

Задает порядок отображения трассировочной информации SortByTime

Трассировочная информация сортируется по времени, то есть в порядке возникновения событий (по умолчанию)

SortByCategory

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

Определяет,

доступен ли просмотр трассировки со всех клиентов или только с Web-сервера

True

Просмотр трассировки возможен только в консоли сервера (по умолчанию)

False

Просмотр трассировки возможен не только на Web-сервере, но с любого клиента

На рис. 4-15 все трассировочные сведения принадлежат к категории aspx.page. Трассировка генерируется автоматически рабочей средой .NET, а в исходном тексте страницы нет никаких трассировочных инструкций. Но это только часть возможностей трассировки в среде ASP.NET. Допустим, что страница входа в систему


Основы разработки приложений в ASP.NET

129

из листинга 4-6 отвечает не так, как ожидается. Возможно, дело в том, что следующий участок программы работает неверно: if ((UserEmail.Value == "doug@programmingasp.net") && (UserPass.Value == "password"))

!

FormsAuthentication.RedirectFromLoginPage( UserEmail.Value, false);

I else

{

Hsg.Text = "Invalid Credentials: Please try again";

! Класс Trace позволяет добавить пользовательские операторы трассировки: if ((UserEmail.Value == "doug@programmingasp.net") && (UserPass.Value == "password")) { Trace.Write("MyCategory", "Authenticated"); FormsAuthentication.RedirectFromloginPage( UserEmail.Value,false);

}

else

i

Hsg.Text = "Invalid Credentials: Please try again"; Trace.Write("MyCategory", "Invalid Credentials");

\ Если внести данные изменения и открыть страницу (листинг 4-6), указав неверные имя пользователя и пароль, в списке трассировочных сообщений появится строка в категории MyCategory (рис. 4-16). Кроме описанных в этой глазе разделов файла Web.config, есть еще несколько разделов, которые практически ничем не отличаются от аналогов в ASP (например, раздел globalization).


130

Session :d: avrn2clS5;ua*«531l. Time of Request: 9/12/2001 12:23:25 I Request Encoding: Unicode (ит-в)

Глава 4

oding:

-Определенная пользователем трассировочная информация

Рис. 4-16. Трассировка с явным образом добавленными категориями

Заключение Важно не забывать, что so многих случаях значения по умолчанию работают удовлетворительно. На самом деле большинства описанных разделов в файлах Web.config нет, так как достаточно значений по умолчанию. Кроме того, такие средства, как Visual Studio .NET особым образом трактуют некоторые из этих параметров, поэтому поведение по умолчанию в Visual Studio .NET подчас несколько отличается от описанного поведения по умолчанию в ASP.NET. Пользователи Visual Studio .NET заметят и другие существенные отличия, чего не скажешь о тех, кто пользуется редактором, не имеющим отношения к .NET. В целом, большинство примеров в дальнейших главах сделаны в Visual Studio .NET, но при этом особое внимание я уделяю «магическим средствам», которые предоставляет Visual Studio .NET. Среда Visual Studio .NET поддерживает весьма полезные вещи, но понимание того, что происходит «за кулисами», помогает в случаях, когда поведение Visual Studio .NET отличается от желаемого.


Основы разработки приложений в ASP.NET

131

Сейчас вы знаете достаточно, чтобы перейти к главе 5, в которой я расскажу о наиболее важном виде приложений ASP.NET Web-формах. Они предоставляют программистам ASP.NET возможности RAD-среды (Rapid Application Development — быстрая разработка приложений) для разработки серверных приложений, о которой ASP-программистам приходится только мечтать. Мечта стала реальностью, и в главе 5 вы узнаете, как заставить ее работать!


Глава 5

Наиболее общая задача динамических Web-приложений — получение данных, введенных пользователем, их обработка и вывод предупреждения в случае обнаружения ошибок. В HTML предусмотрено много элементов управления, в том числе текстовые поля, раскрывающиеся списки, списки выбора, флажки и переключатели, а также обычные кнопки и кнопки отправки данных. Поддержка форм в HTML — тот фундамент, на котором в ASP строится система дополнительной обработки и проверки входных данных. Проверка входных данных в HTML-формах отличается от аналогичной процедуры в обычных формах приложений Microsoft Windows. Например, значение 5/35/2001, введенное в поле, которое предназначено для даты, некорректно. В отличие от приложений Microsoft Visual Basic 6.0 или на основе Windows-форм в ASP.NET не предусмотрен удобный способ создания поля для ввода по маске, в которых некорректные данные отбрасываются автоматически. В ASP.NET также нет аналога DateTimePicker —стандартного элемента управления Windows, автоматически обеспечивающего корректность всех элементов даты — года, месяца и дня.

Примечание Конечно, на локальном уровне вы вправе применять JavaScript для управления данными, вводимыми в Web-формы, и предотвращения некорректных вариантов вроде даты 5/35/2001, но обычно в Web-приложениях так не делают. Разработчики, использующие ASP, как правило, создают двойную линию обороны: основную проверку на стороне клиента с применением JavaScript, и на случай, если проверка на стороне клиента неэффективна из-за того что браузер не поддерживает нужных функций, на стороне сервера выполняется дополнительная проверка.


Web-формы

133

Классическая архитектура ASP-программы Так как в ASP практически отсутствует встроенная поддержка проверки правильности входных данных, разработчикам приходится рассчитывать только на свои силы. Зачастую даже в одной группе разработчиков применяются разные способы получения и проверки вводимой пользователем информации. Я вынужден констатировать, что даже в исходном тексте одного разработчика иногда используются несколько способов проверки правильности входных данных. За примером далеко ходить не надо — я один из таких разработчиков. В каждой HTML-форме в атрибуте action тэга <FORM> указан 1 определенный URL-адрес в абсолютном (начинается с префикса http:A или относительном формате (имя файла в текущем каталоге). Относительный URL-адрес иногда начинается с символа «наклонная черта» (/}, тогда поиск ведется от корневого каталога текущего Web-узла. При отправке формы ее данные передаются по URL-адресу, указанному в атрибуте action тэга <FORM>. На рис. 5-1 и 5-2 показаны схемы двух наиболее популярных в ASP способов проверки правильности вводимых данных. Default.asp

Страница \ с ASP-формой !

Корректнь Отправка страницы S ли введенные ,данные?Х

Да перенаправить на другую страницу

AcceptData.asp Рис. 5-1. Первый способ проверки правильности входных данных формы Вообще говоря, форма может и не содержать атрибут action. В этом случае для обращения к серверу в процессе ее обработки применяется URLадрес самой формы. — Прим. перев.


134

Глава 5

Default.asp J

Страница eASPформой

Еще одна страница

Отправка страницы Да —перенаправить на другую страницу

Рис. 5-2. Второй способ проверки правильности входных данных формы На схеме на рис. 5-1 в форме на странице Default.asp атрибут action тэга < FORM> указывает на AcceptData.asp. Задача AcceptData.asp — проверить корректность данных формы и либо возвратить пользователя на Default.asp, если обнаружена ошибка в данных, либо вывести информационное сообщение, либо перенаправить пользователя на другую страницу. На рис. 5-2 атрибут action тэга <FORM> указывает на Default.asp. Страница ссылается на себя же, то есть данные, введенные в форму, отправляются на эту же страницу. Поведение страницы должно меняться в зависимости от того, отображается ли форма первый раз или после отправки данных на сервер (postback). Существует несколько способов решения этой задачи, но я предпочитаю использовать скрытое поле формы postback. У каждой из схем (рис. 5-1 и рис. 5-2) есть свои преимущества, но мне больше нравиться способ, показанный на рис. 5-2. На то есть ряд причин: • вся логика управления формой сосредоточена в одном месте. Это удобно, если возникает необходимость добавить или удалить элемент управления; • если возникает ошибка, для ее исправления не нужно перенаправлять пользователя на другую страницу. Кроме того, этот метод полезен в случаях, когда содержимое окна обновляет-


Web-формы

135

ся в процессе первичного заполнения формы. Недавно я работал над системой регистрации в больнице. В этой системе при регистрации нового пациента пользователь вначале указывал вариант обслуживания — амбулаторный или стационарный, и во втором варианте в форме появлялся новый элемент управления, где следовало указать номер палаты и место; • в случае ошибки переданные данные не нужно восстанавливать для перенаправления на исходную страницу. Обычно при успехе отправки формы ее данные обрабатываются, и пользователь перенаправляется на другую страницу, Способ перенаправления на другую страницу для проверки правильности входной информации имеет определенные достоинства. Основное — возможность перенести одинаковые проверки с нескольких страниц на одну. Это полезно, если для проверки применяется объемный и сложный код. Альтернативой подобной централизации считается использование подключаемых файлов, но и при этом возникают некоторые проблемы. Проверка корректности входных данных в ASP.NET структурирована гораздо лучше, чем в ASP. Цель создания ASP.NET — применить RAD-nogxog (Rapid Application Development — «быстрая разработка приложений») к программированию серверной части. Эта цель в значительной степени достигнута.

Отличия ASP.NET В ASP.NET предусмотрены более богатые возможности проверки вводимых пользователем данных, чем в ASP, но для полноценного их применения следует придерживаться схемы, показанной на рис. 5-2, согласно которой страница отправляет данные самой себе. Вы вправе попробовать другой способ, но это вряд ли вам понравится, так как придется плыть против, а не по главному течению ASP.NET. Как говаривал один из героев «Звездных войн»: «Используй Силу, Люк!» Формы ASP.NET и Visual Basic 6.0 Большая часть этой главы посвящена сравнению «классического» А5Р-программирования и ASP.NET. Отличия рази-


136

Глава 5

тельны. Еще заметнее они в методах создания форм при переходе от Visual Basic 6.0 к ASP.NET, причем неважно, что вы в конечном итоге выберете — Visual Basic .NET или С#. Некоторые вещи, которые просто и легко выполняются в традиционных приложениях Visual Basic, существенно сложнее сделать в модели форм среды ASP.NET. Например, в традиционных приложениях очень часто используется изменение содержимого элементов управления при переходе между ними. Я работал над приложением, в котором пользователь выбирал тип оборудования из раскрывающегося списка. После выхода списка выбора типа оборудования из фокуса раскрывающийся список с перечнем устройств изменялся — в нем оставались только соответствующие выбранному типу наименования. Изменение элементов управления «на лету» не очень рекомендуется в формах ASP.NET, так как при изменении содержимого элемента управления обычно требуется дополнительное обращение к серверу. На более глубоком уровне удивление разработчиков на Visual Basic, начинающих работу с формами ASP.NET, наверняка вызовет время жизни страницы и всех определенных на ней переменных. Страница ASP.NET похожа на забывчивого ребенка: вы можете говорить ему что-то (задавать значения переменных-членов базового класса страницы), но когда при следующем просмотре страницы поинтересуетесь значением переменной, окажется, что он напрочь все «забыл». Такое «беспамятство» не покажется странным, если вспомнить, что протокол HTTP не поддерживает состояния. При работе с крупным Web-сайтом (например, MSDN) нет никаких гарантий, что при запросе одной и той же страницы ответ придет с того же Web-сервера. Один из способов сохранения информации о состоянии состоит в использовании свойства ViewState. Оно хранит значения между обращениями к серверу в скрытой переменной VIEWSTATE на странице. Значение этой переменной не очевидно, и получить его нелегко, причем ее значение не рекомендуется изменять напрямую.


Web-формы

137

Допустим, у вас есть целочисленная переменная tries —член класса, используемая для подсчета числа перезагрузок страницы пользователем. Для сохранения ее значения можно воспользоваться следующим выражением: ViewState ("tries")=tries

С другой стороны, значение можно инкапсулировать в свойство класса. В Visual Basic .NET это выглядит примерно так: Public Property tries() As Integer Get Return CInt(ViewState ("tries")) End Get Set(ByVal Value As Integer) ViewState("tries") = Value End Set End Property Когда применяется свойство, вы напрямую обращаетесь к MyClassI .tries в коде на Visual Basic .NET, но на самом деле значение считываться и сохраняться в свойстве ViewState, Путаницу усугубляет то, что некоторые значения сохраняются автоматически между обращениями к серверу. По умолчанию в ASP.NET автоматически сохраняются значения, введенные в элементы управления, и свойства объявленных на странице элементов управления.

Пример проверки корректности вводимых данных в формах ASP.NET В главе 4 я рассказал об аутентификации с помощью форм и в листинге 4-6 показал простую страницу, где вводится адрес электронной почты пользователя и пароль, которые затем сравниваются с жестко определенными в коде разрешенными значениями. В листинге 5-1 демонстрируется та же форма. <Х@ Import Namespace="System.Web.Security " X> <html> <script language="C#" runat=server> void Login_Click(Object sender, EventArgs E)


138

Глава 5 // Аутентификация пользователя: в этом примере // вход разрешается // лишь пользователю doug@prograromingasp.net с паролем // 'password' if ((UserEmail. Value == "doug@programmingasp.net") && (UserPass. Value == "password")) { FormsAuthentication. RedirectFromLoginPage( UserEmail. Value, false); 1 else ! Msg.Text = "Invalid Credentials: Please try again";

</script> <body> <form runat=server> <center> <h3> <font face="Verdana" color=blue>Login Page</font> </h3> <table> <tr> <td> Email: </td> <td> <input id="UserEmail" type="text" runat=server size=30 /> </td> <td> <ASP:RequiredFieldValidator Con trolToValidate= "User Email" Display="Static" ErrorMessage="*" runat=server /> </td> </tr> <tr> <td> Password: </td> <td>


Web-формы

139

<input id="UserPass" type=password runat=server size=30 /> </td> <td> <ASP:RequiredFieldValidator ControlToValidate="UserPass" Display="Static" ErrorMessage="*" runat=server /> </td> </tr> <tr> <td colspan=3 align="center"> <asp:button text="Login" OnClick="Login_Click" runat=server> </asp:button> <P> <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server /> </td> </tr> </table> </center> </form> </body> </html> Листинг 5-1. Страница аутентификации пользователя Login.aspx Login.aspx имеет много общего как с обычной ASP-страницей, так и с традиционной HTML-страницей. В начале страницы располагается инструкция import для импорта пространства имен System.Web.Security. Как вы помните, на 2страницу Login.aspx пользователи перенаправляются при первом посещении Webузла — это определено в конфигурационном файле Web.config. Пространство имен System. Web.Security используется для последующего корректного перенаправления пользователя на первоначально запрошенную страницу. Отличия ASP.NET В традиционном ASP-программировании один из способов импортирования отдельных функций заключался в использовании вы-


140

Глава 5

ражений include. В ASP.NET поддерживается выражение import, позволяющее импортировать пространства имен. Однако реализация .NET, в отличие от Java, не допускает символов подстановки, то есть нельзя импортировать System.Web.* и затем обратиться к пространству имен System. Web.Security. За начальным тэгом <НГМ!_> следует блок сценария, выделенный тэгами <SCR!PT> и </SCRIPT>. Блок сценария содержит одну функцию Login_C!ick на языке С#. Она сравнивает значения, полученные из формы, с жестко определенными в коде значениями и либо использует метод из System.Web.Security.FormsAuthentication для возвращения пользователя на изначально запрошенную страницу, либо отображает п надписи формы текст, в котором пользователю предлагается повторить попытку.

Отличия ASP.NET В ASP функции выделялись либо тэгами <SCRIPT> и </SCRIPT>, как в листинге 5-1, либо тэгами <% и %>. В ASP.NET допустим только первый вариант. В настоящее время сообщение об ошибке, информирующее о применении «неправильных» тэгов <% и %> для выделения объявлений функций, не совсем внятно. Однако эти тэги разрешается применять в теле страницы для вывода результатов. В любом случае (как вы вскоре увидите) есть более удачный способ программирования приложений ASP.NET. Обратите внимание, что в сценарии, выделенном тэгами <SCRIPT> и </SCRIPT>, функция Login_CHck никогда не вызывается напрямую. Как ее вызвать, я объясню чуть позже. В теле страницы (обрамленной тэгами <BODY> и </BODY>) форма начинается с тэга <FORM>. В отличие от тэга <FORM> в обычном HTML или ASP, здесь указывается единственный атрибут— runat, которому присваивается значение «server». He указано, какой метод использовать —post или get, а также отсутствует атрибут action, в котором указывается страница, которую следует вызывать при отправке формы. В ASP.NET форма со значением «server» в атрибуте runat всегда отправляет данные самой себе. При программировании на «классическом» ASP атрибут


Web-формы

141

runat не используется в таком контексте, но ASP-программисты знают, что он применяется в блоках сценариев. В ASP.NET атрибут runat применяется во многих HTML-тэгах, и его наличие всегда указывает, что для обработки соответствующего компонента требуется обращение к серверу. Форма содержит большое количество обычного HTML-кода, в том числе таблицы и текстовые поля ввода. V последних есть одна незнакомая особенность — все та же пара «атрибут — значение» runat=server, которая присутствует в тэге <FORM>.

Серверные элементы управления в ASP.NET и в HTML В листинге 5-1 вы наверняка заметили несколько незнакомых тэгов. Все они начинаются с сочетания <ASP:, после которого следует текст — в одних местах знакомый (ASP:Button или ASP:iabel], а в других — нет (ASP:RequiredFieldValidator). Так обозначаются серверные элементы управления ASP.NET. Они выполняются на сервере и отчасти похожи на HTML-элементы управления со значением server G атрибуте runat. Элементы управления с атрибутом runat способны вызывать серверные функции. В этом примере метод LoginjClick вызывается при нажатии кнопки — серверного элемента управления ASP.NET. Но если эти элементы управления идентичны, зачем поддерживать оба набора? На то есть несколько причин. Во-первых, для некоторых элементов управления не существует аналогов в «чистом» HTML. Очень просто построить серверный HTML-элемент управления типа «поле ввода» или «кнопка» с помощью стандартного тэга HTML и пары runat=server, но для элемента управления вроде RequiredFieldValidator в HTML нет аналога. Однако, прежде чем погрузиться в изучение RequiredFieldValidator, я познакомлю вас с основными различиями между двумя типами серверных элементов управления — HTML и ASP.NET. Серверные HTML-элементы управления обладают следующими особенностями: • имеющаяся объектная модель позволяет программно манипулировать элементами управления; • модель событий позволяет обрабатывать события элементов управления точно так же, как при обработке событий на стороне клиента, но вся работа выполняется на стороне сервера;


142

Глава 5

возможность обрабатывать события на стороне клиента, на стороне сервера или одновременно и там, и там. Обработка событии и на стороне клиента, и на стороне сервера может показаться излишней, но на то есть ряд серьезных причин. О распределении «обязанностей» по обработке между сервером и клиентом я расскажу в главе 7;

автоматическое сохранение значений между обращениями к серверу. Например, текст, указанный в поле ввода, остается в нем и после отпразки данных;

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

• привязка данных к одному или нескольким свойствам элемента управления; • поддержка таблиц стилей HTML 4.0 при условии поддержки их браузером; • сквозные пользовательские атрибуты — к серверному элементу управления HTML можно добавлять атрибуты, а среда .МЕТ Framework читает и отображает их без изменения функциональности. Серверные элементы управления в ASP.NET обеспечивают такие же возможности, а также кое-что еще. Однако между серверными элементами ASP.N ЕТ и HTML нет однозначного соответствия. (Например, для элемента управления Requt'redFieldValidator нет стандартного аналога в HTML.) Серверные элементы управления ASP.NET обладают следующими особенностями: •

развитая объектная модель обеспечивает контроль типов при программировании;

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

изменение внешнего вида и поведения некоторых элементов управления с помощью шаблонов (Вниманию программистов на языке C++: это не те шаблоны, которые вы знаете!);


Web-формы

143

для некоторых элементов управления можно задать кэширование их событии до отправки данных формы или немедленную отправку событий на сервер;

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

На странице Login.aspx (листинг 5-1} присутствуют серверные элементы управления и HTML, и ASP.NET. В этой книге в большинстве примеров применяются серверные элементы управления ASP.NET. Программистам, привыкшим к работе на языках со строгим контролем типов, например С и C++, удобнее пользоваться серверными элементами управления ASP.NET, так как в них применяется модель с контролем типов.

Элементы управления проверки правильности вводимых данных Первый элемент управления, с которым я познакомлю вас в этом разделе, — RequiredFieldValidator. Посмотрите на его иерархию классов (рис. 5-3). Как вы знаете, в .NET Framework общим для всех предком является Object, и нет ничего удивительного в том, что RequiredFieldValidator — его дальний потомок.

Control V\febControl

BaseValidaior RequiredReldV

Рис. 5-3. Иерархия классов для RequiredFieldValidator в .NET Framework


144

Глава 5

Элемент управления RequiredFieldValidator Чтобы лучше понять эту иерархию, следует разобраться с тем, как работает RequiredFieldValidator. Этот элемент управления применяется з программе Login.aspx (листинг 5-1). На рис. 5-4 показана страница Login.aspx после отправки обоих незаполненных полей.

Ad*ess €J httptf/locehost/fotmsauth /b д in. a ipx

Login Page Email Password:

Рис. 5-4. Login.aspx после нажатия кнопки Login при незаполненных полях V всех элементов управления проверки правильности данных имеется атрибут ErrorMessage. В нашем примере его значение состоит из символа «ззездочка» (*). Таким образом, если в поле введены некорректные данные, рядом с ним отображается «звездочка». RequiredFieldValidator — практически самый простой элемент из проверяющих входные данные; он проверяет, указано ли 8 поле хоть что-нибудь. Так как же работает элемент управления проверки данных? Его работа определяется несколькими обстоятельствами. Как часто бывает при работе с ASP.NET, чтобы разобраться, каким образом запрос элемента управления проверки входных данных преобразуется в нечто, понятное браузеру, полезно взглянуть на HTML-код, который попадает в браузер. В листинге 5-2 приведен HTML-код, который отправляется браузеру до срабатывания элементов управления, проверяющих данные (для удобства чтения листинг отформатирован).


Web-формы

145

<html> <body> <form name="_ctlO" method="post" action="login.aspx" language="javascript" onsubmit="ValidatorOnSubmit();" id="_ctlO"> <input type="hidden" name="__VIEWSTATE" value="dDwxMDgxMzYzOTAxOzs+" /> <script language="javascript" src="/aspnet_client/system_web/1_0_3217_0/ WebUIValidation.]s"> </script> <center> <h3> <font face="Verdana" color=blue>Login Page</font> </h3> <table> <tr> <td> Email: </td> <td> <input name="UserEfnail" id="UserEmail" type="text" size="3(T /> </td> <td> <span id="_ct!1" controltovalidate="UserEmail" errormessage="«" evaluationfunction= "RequiredFieldValidatorEvaluatelsValid" initialvalue="" style="color:Red;visibility:hidden;">*</span> </td> </tr> <tr> <td> Password: </td> <td> <input name="UserPass" id="UserPass" type="password" size="30" /> 63ак. 422


146

Глава 5 </td> <td>

<span id="_ctl2" controltovalidate="UserPass" errormessage="*" evaluationfunction= "RequiredFieldValidatorEvaluatelsValid" initialvalue="" style="color:Red;visibility:hidden;">*</span> </td> </tr> <tr> <td colspan=3 align="center"> <input type="submit" name="_ct!3" value="Login" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" /> <P> <span id="Hsg" style="color:Red;font-family: Verdana; fontsize: 10pt;"> </span> </td> </tr> </table> </center> <script language="javascript"> <! — var Page_Validators = new Array(document.all["_ctl1"], document.all[" ct!2"]); // -> </script> <script language="javascript"> <l — var Page_ValidationActive = false; if (typeof(clientlnformation) != "undefined" && clientlnformation.appName.indexOf("Explorer") != -1) I if (typeof(Page_ValidationVer) == "undefined") alert("Unable to find script library " +


Web-формы

147

'"/aspnet_client/system_web/1_0_3217_0" + "/WebUIValidation.js'. " + "Try placing this file manually, " + 1 "or reinstall by running 'aspnet_regiis -c ."); else if (Page_ValidationVer != "125") alert("This page uses an incorrect " + "version of WebUIValidation.js. The page expects " + "version 125. The script library is " + Page_ValidationVer + "."); else ValidatorOnLoadO;

} function ValidatorOnSubmitQ { if (Page_ValidationActive) { Validate rCommonOnSubmitO; } I // --> </script> </form> </body> </html> Листинг 5-2. HTML-кол передаваемый браузеру при запросе Login.aspx до срабатывания элементов управления проверки данных Примечание Обратите внимание: в листинге 5-2 есть необычное скрытое поле VIEWSTATE. Оно применяется для хранения информации о состоянии элементов управления между обращениями к серверу. Не надо его трогать. Его создатели позаботились, чтобы вы не смогли его изменить и, к примеру, подменить информацию о состоянии другого пользователя. Ого, сколько кода! Понятно, почему блок сценария на С#, расположенный в начале листинга 5-1, отсутствует з листинге 5-2, он отмечен как выполняемый на сервере (runat=server). Однако присутствует новый блок <SCRIPT>;


Глава 5

148 <script language="javascript" src="/aspnet_client/system_web/1_0_3217_0 WebUIValidation.js"> </script>

Этот фрагмент кода выглядит совсем незнакомым! Взглянув в консоль Internet Information Services (рис. 5-5), вы увидите, что логический каталог, на который указывает атрибут src тэга <SCRIPT>, действительно существует на US-сервере. - 1 Internet Inrorm.ll-Hin Spivjrf-s

уяа *] WebUIVaSdation.is [*|5martrJavlE5.1s ^f]5martNav,is

i Printers i CrystaReportWetfcrmVieiver i FormsAuth aspnet_clent jjjjj system_web udmmi5t ration Web Site Default 5MTP Virtual Server

•!

Рис. 5-5. Каталог с клиентским кодом ASP.NET в консоли Internet Information Services Я не привожу содержимое файла WebUIValidation.js, так как версия, установленная у вас, может отличаться от моей. Важно то, что при выполнении проверки на стороне клиента используется специально предназначенная для этого библиотека.

Примечание Местоположение клиентских сценариев также можно задать с помощью тэга webControls в файле Machine.config. Как правило/ не рекомендуется менять местоположение клиентских сценариев, но возможность выяснить его иногда оказывается полезной.


Web-формы

149

Следующее значительное изменение в результирующем HTMLкоде — тэг <SPAN>, похоже, заменивший собой первый RequiredFieldValidator: <span id="_ctlT controltovalidate="UserEmail" errormessage="*" evaluationfunction= "RequiredFieldValidatorEvaluatelsValid" initialvalue="" style="color:Red;visibility:hidden;">*</span> HTML-тэг <SPAN> выполняет роль контейнера для фрагмента текста, требующего особого способа отображения. В данном случае этот «особый способ» заключается в том, что текст невидим. Для этого применяется стандартный атрибут style. Элемент управления проверки входных данных, связанный с полем ввода пароля Password, просто преобразуется в тэг <SPAN>. Элемент <ASP:Button> из листинга 5-1 преобразуется в традиционную HTML-кнопку отправки данных: <input type="submit" name="_ctl3" value="Login" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" /> Как видите, требуемая проверка теперь выполняется JavaScriptсценарием на стороне клиента. Но как именно? Тэг сценария, расположенный в тексте после формы, на самом деле устанавливает переменную-массив Page_Valid3tors, каждый элемент которого соответствует одному из элементов управления проверки правильности данных. Событие onclick кнопки отправки данных вызывает функцию Page _ClientValid ate. В моей версии WebUI Validation.]s она выглядит примерно так: function Page_ClientValidate() { var i; for ( 1 = 0 ; i < Page_Validators.length; ValidaterValidate(Page Validators[i]); } Validate rUpdatelsValidO; ValidationSummaryOnSubmitO; Page_BlockSubmit = !Page_IsValid; return Page IsValid;


150

Глава 5

Корректность отдельных элементов массива Page_Validators проверятся посредством еще одной функции из WebU/Validation.js — ValidatorValidate. Она вызывает функцию, указанную в атрибуте evaluation Function тэга <SPAN >, для каждого элемента управления проверки данных. Подробности всего этого механизма не очень важны, но вы должны знать, где выполняется обработка.

Элемент управления CompareValidator В Login.aspx (листинг 5-1) видно, как введенные адрес электронной почты и пароль сравниваются с жестко определенными в коде значениями. Если адрес и пароль не совпадают с этими значениями, текст другой надписи формы (он отображается при неудаче проверки) предлагает пользователю повторить попытку. До сих пор мы не видели этого сообщения, так как при нажатии кнопки Login на стороне клиента и до отправки формы на сервер срабатывали проверочные элементы, отображая те самые красные звездочки. Входные данные не проходили проверку на странице, и до обращения к серверу дело не доходило. В ASP.NET есть элемент управления CompareValidator, который выполняет сравнение двух значений. Он полезен, к примеру, при создании страницы смены пароля, на которой новый пароль должен вводиться дважды, чтобы обеспечить точность его определения пользователем. Попробуем применить элемент управления CompareValidator вместо серверной функции Login_CHck. В нашем случае элемент управления RequiredFt'eldValidator можно заменить на CompareValidator таким образом: <asp:CompareValidator id="comp1" ControlToValidate="UserPass" ValueToCompare = "password" Type="String" runat="server"/> В CompareValidator для указания сравниваемого значения можно воспользоваться атрибутами ValueToCompare или СотрлгеToControl. При установке последнего равным ID другого элемента управления на форме CompareValidator сравнивает значение атрибута ControlToValidate со значением элемента управления, на который указывает CompareToControl. При использовании атрибута ValueToCompare проявляется неприятный побочный эффект. Если воспользоваться кодом из предыдущего примера, после обработки Compare Validator браузеру возвратиться следующий текст:


Web-формы

151

<span id="comp1" controltovalidate="UserPass" evaluationfunction="CompareValidatorEvaluateIsValicf" valuetocompare="password" style="color:Red;visibility:hidden;"></span> Скорее всего, это we то, что вам нужно. В полученном браузером HTML-кодетэг <SPAN> в явном виде содержит атрибут ValueТоCompare. Это, конечно, надуманный пример, но в реальной жизни вы вряд ли захотите предоставлять клиенту слишком много информации. Одно из решений — изменить атрибут clienttarget директивы Page. В листинге 5-1 этой директивы не было, но ее можно добавить: <Х@ Page Language="c#" clienttarget=downlevel K> При добавлении этой директивы к файлу Login.aspx (листинг 5-1) вместо HTML-кода, показанного в листинге 5-2, браузер получит текст, который показан в листинге 5-3, <html> <body> <form пате="_с110" method="post" action="login.aspx" id="_ctlO"> <input type="hidden" name="__VIEWSTATE" value="dDwxMDgxHzYzOTAxOzs+" /> <center> <n3> <font face="Verdana" color=blue>Login Page</font> </h3> <table> <tr> <td> Email: </td> <td> <input nafne="UserEmail" id="UserEmail" type="text" size="30" /> </td> <td> &nbsp;


152

Глава 5 </td> </tr> <tr> <td> Password: </td> <td> <input name="UserPass" id="UserPass" type="password" si?e="30" /> </td> <td> &nbsp; </td> </tr> <tr> <td colspan=3 align="center"> <irput type="submit" name="_ct!3" va]ue="Login"

onclick="if (typeof(Page_CHentValidate) == 'function') Page_ClientValidate(); " language="javascript" /> <p> <sfian id="Msg"> <font face="Verdana" color="Red" size="2"> </font> </span> </td> </tr> </table> </center> </form> </body> </html> Листинг 5-3. HTML-код, получаемый браузером, если код Login.aspx (листинг 5-1) содержит атрибут dienttarget=downlevel в директиве Page


Web-формы

153

Примечание В текущей версии ASP.NET использование пары «атрибут — значение» dienttarget—downlevel в директиве Page также приводит к тому, что возвращаемый HTML-код соответствует спецификации HTML 3.2. Это чревато возможными сложностями. Надеюсь, в следующих версиях ASP.NET появится механизм более тонкого управления версией возвращаемого браузеру HTML-кода. Очевидно, что применение dienttarget=downlevel приводит к созданию более «чистого» HTML-кода! При использовании устаревшего браузера или даже браузера, отличного от Microsoft Internet Explorer версии 4.0 или старше, код страницы станет напоминать текст листинга 5-3, даже если не задать clienttarget= downlevet. Одно из самых заметных изменений в коде — ячейки таблицы, ранее содержавшие тэги <SPAN> с элементами проверки данных, теперь содержат неразрывный пробел (&nbsp). Кроме того, вследствие такого «занижения» версии HTML изменяется поведение формы при отправке данных. В частности, после нажатия кнопки Login при незаполненных обоих полях отображается страница, показанная на рис. 5-6.

ИЗI httpi/flocahos^Formsauthflooin.aspx

Login Page Email1 Password: :

Рис. 5-6. Страница, отображаемая после нажатия кнопки Login при незаполненных полях ввода, когда искусственно занижается версия HTML для отображения в устаревших браузерах


154

Глава 5

Страница на рис. 5-6 немного отличается от страницы, отображаемой при нажатии кнопки Login на странице, не предназначенной для устаревших браузеров {см. рис. 5-4). Эта страница содержит сообщение «Invalid Credentials: Please try again» («Неверные реквизиты. Попробуйте еще раз»). Важно, что это сообщение поступает от функции iogm_cHck, выполняющейся на стороне сервера. Судя по тому, что этот код выполнился, страница создана после обращения к серверу. Использование устаревшего браузера или принудительное занижение версии HTML приводят к увеличению числа обращений к серверу, хотя иногда оно того стоит. V элемента управления CompareValidator имеется ряд дополнительных атрибутов. Полное их описание приведено в MSDN, но с самыми полезными из них — Туре и Operator — я познакомлю вас подробнее. Атрибут Туре позволяет указать тип сравниваемых данных. Вот допустимые значения этого атрибута: • String — сравнение строковых значений; •

Integer — сравнение целочисленных значений;

Double — сравнение чисел с плавающей точкой;

• Date — сравнение дат; • Currency — сравнение денежных значений. Атрибут Operator применяется для управления типом выполняемого сравнения. В примерах из этой главы используется значение этого атрибута по умолчанию — Equal. В этом случае выполняется проверка равенства значений проверяемого элемента управления и элемента управления, указанного в ControlToCompare константного значения, которое задано в ValueToCompare. Вот перечень допустимых значений Operator. • GreaterThan —больше; • GreaterThan Equal — больше или равно; • LessThan —меньше; •

LessThanEqual — меньше или равно;

NotEqual — не разно.


Web-формы

155

V Operator есть еще одно разрешенное значение — DataTypeCheck. Этот режим применяется для проверки соответствия или приводимости введенных данных к типу, указанному в атрибуте Туре. DataTypeCheck можно, в частности, использовать для проверки того, является ли введенное пользователем значение корректной датой. В показанном далее фрагменте кода проверяется корректность даты, введенной в поле ввода: <asp: TextBox id=txtDate runat="server"/> <asp:CompareValidator ControLToValidate="txtDate" Operator="DataTypeCheck" Type="Date" runat="server"> Must input a date </asp:CompareValidator> Такая проверка типа данных значительно проще той, что потребовалась бы при разработке «классической» ASP-страницы, причем элемент проверки данных генерирует клиентский код там, где это действительно уместно (если только явно не присвоить атрибуту ciienttarget значение downlevel, то есть принудительно снизить версию создаваемого HTML-кода).

Другие элементы проверки данных Кроме RequiredFieldValidator u CompareValidator есть и другие полезные элементы проверки данных. Они перечислены ниже. • RangeValidator проверяет, располагается ли введенное пользователем значение в пределах заданных границ. Допустимые значения верхней и нижней границ — числа, строки или даты. Эти значения определяют напрямую или вычисляются на основании значений других элементов управления. •

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

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


Глава 5

156

В листинге 5-4 показана страница, на которой имеются проверяющие элементы всех трех типов. <*$ Import Namespace="System. Web. Security " %> <html> <script language="Ctf" runat=server> void Validate Click(0bject sender, EventArgs E) { if ( Page.IsValid } { Msg.Text="Page Valid";

void CustomServerVal (object source, ServerValidateEventArgs args) { try ! if ( args. Value. Equals("Hello") ) Msg.Text="ServerValidation called and TRUE returned. "; args.IsValid=true; I else Msg.Text="ServerValidation called and FALSE returned. "; args. IsValid=false; } I catch { Msg.Text="ServerValidation called and FALSE returned. args. IsValid=false;

</script> <body> <form runat=server> <center> <h3> <font face="Verdana" color=blue>Validator Test Page</


Web-формы font>

157

</h3> <table> <tr> <td> Range Validation (1-12): </td> <td> <input id="Range" type="text" runat=server size=10 /> </td> <td> <ASP:RangeValidator ID="ValRange" ControlToValidate=" Range" Oisplay="Static" Type="Integer" MinimumValue="1" MaximumValue="12" ErrorMessage="Out of Range" runat=server /> </td> </tr> <tr> <td> Regular Expression Validation (nnn-nn-nnnn): </td> <td> <input id="RegEx" type="text" runat=server size=11 /> </td> <td>

<ASP:flegularExpressionValidator ID="ValRegEx" ControlToValidate="RegEx" runat="SERVER" ErrorMessage= "Enter a valid U.S. SSN (nnn-nn-nnnn)." ValidationExpression= </td> </tr> <tr> <td> Custom Validation


158

Глава 5

(It wants you to enter "Hello" WITHOUT THE QUOTES): </td> <td> <input type="text" id="txtCustom" runat=server size=11 /> </td> <td> <ASP:CustomValidator ID="ValCustom" runat="server" ControlToValidate="txtCustonr OnServerValidate="CustomServerVal" Display="Static" > Enter "Hello". Case-Sensitive. </ASP:CustomValidator> </td> </tr> <tr> <td colspan=3 align="center"> <asp:button text="Validate" OnClick="Validate_Click" runat=server> </asp:button> <P> <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server /> </td> </tr> </table> </center> </form> </body> </html> Листинг 5-4. Страница ValidatorTest.aspx, на которой используются элементы управления RangeValidator, RegularExpress! onValidator и CustomValidator На рис. 5-7 показано, как ValidatorTest.aspx отображается 8 браузере.


Web-формы

159

3 h«p://lnr,iltio<>t ,'validatotlest fVjtlittiifcei-Tett.aspx - Microsoft!

Validator Test Page

Я

Range Validation (1-12): Regular Expression Validation (nnn-m-nnim):

]

Custom Validation (It wants you to eater "Hello" "WITHOUT THE QUOTES)

Рис. 5-7. Отображение страницы ValidatorTest.aspx в браузере Существует несколько атрибутов, характерных лишь для элемента управления RangeValidator. Вот фрагмент кода из листинга 5-4 с объявлением RangeValidator. <ASP:RangeValidator ID="ValRange" ControlToValidate="Range" Display="Static" Type="Integer" MinimumValue="1" MaximumValue="12" ErrorMessage="Out of Range" runat=server />

Три атрибута (ControlToValidate, Type и RunAt) вам уже знакомы, еще один приводился ранее без объяснения (Display —описывается в следующем разделе), кроме того, появились два новых атрибута (MinimumValue и MaximumValue). Type определяет тип сравнения. Например, что больше — «1234» или «13»? Если это строковые значения, то при сравнении по символам 1234 оказывается меньше, а если числовые, то 1234 больше. В атрибуте Туре в RangeValidator разрешены те же значения, что в аналогичном атрибуте элемента управления CompareValidator. MinimumValue и MaximumValue сравниваются со значением ControlToValidate в соответствии с типом сравнения, определенном в Туре, В этом примере ожидается число в интервале 1—12.


160

Глава 5

Преимущество RegularExpressionVaiidator — з его гибкости. Вот фрагмент кода, в котором создается этот элемент управления: <ASP:RegularExpressionValidator ID="ValRegEx" ControlToValidate="FegEx" runat="SERVER" ErrorMessage="Enter a valid U.S. SSN (nnn-nn-nnnn)." ValidationExpression=-[0-9]{3}-[Q-9]{2KO-9]{4r /> В этом коде есть уникальный атрибут — ValtdationExpression. Его значение определяет шаблон регулярного выражения, с которым сравнивается значение элемента управления, указанного в ControlTo Validate. Тем, кто не знает, что такое «регулярные выражения», я рекомендую прочитать следующую врезку.

Регулярные выражения Регулярные выражения —это строки-шаблоны, предназначенные для сравнения введенного текста. Почему сравнение текста с шаблоном гораздо полезнее, чем сравнение с другой строкой или элементом управления (как это делается в CompareValidator}'* Все дело в природе проверяемых объектов. Часто приходится проверять такие сведения, как телефонные номера, почтовые индексы или номера социального страхования. В этих случаях элемент управления CompareValidator не поможет. Простейший пример регулярного выражения — звездочка в имени файла. В эпоху командной строки практически все пользователи прекрасно знали, как ею пользоваться. Нужно увидеть все файлы с расширением .doc в текущем каталоге? Введите в командной строке: Dir *.doc Результат этой команды — список всех файлов с расширением .doc. Если вы ищете файл, с именем TEST0501.DOC либо TEST0601.DOC, достаточно следующей команды: Dir TEST0701.DOC


Web-формы

161

Программисты, пишущие на SQL, знакомы с формой регулярных выражений, применяющейся с ключевым словом LIKE, как показано ниже: SELECT * FROM Users WHERE LastName LIKE 'fl__lly'

В результате вы получите список пользователей с фамилией Reilly или Rielly — распространенная опечатка. В списке не будет фамилий вроде Rilly, так как в знак подчеркивания (_) является полем подстановки ровно одного символа, а два знака подчеркивания соответствуют двум символам. Регулярные выражения в .NET гораздо мощнее, и их полное описание выходит далеко за рамки этой книги. Поэтому здесь я рассмотрю только регулярные выражения, использованные в примере RegularExpressionExample из файла ValidatorTest.aspx (листинг 5-4). Регулярное выражение «[0-9]{3}-[0-9]{2}-[0-9]{4}» — один из множества способов проверить корректность номера социального страхования в США, который должен соответствовать шаблону ппп-пп-пппп, где п соответствует одной цифре. Символы внутри квадратных скобок ([ и ]) — это либо набор, либо диапазон символов. В этом примере во всех квадратных скобках разрешенные символы представлены диапазоном символов 0—9. За набором символов в квадратных скобках следует число в фигурных скобках ({ и }). Оно показывает число символов, удовлетворяющих предыдущему выражению. На местах, где в шаблоне расположены дефисы (-) вне скобок, должны находится черточки. Существуют и другие способы определения этого шаблона, например, такие: [0123456789] Ш-[0123456789] {2}-[0123456789] {4} \d{3>-VJ{2}=\d{4} В первом случае я просто перечислил все цифры в квадратных скобках. Во втором случае — указал цифры с помощью специального символа \d, за которым следует число в фигурных скобках. Существует очень много других специальных символов. Кроме определения разрешенных символов,


162

Глава 5

для указания запрещенных символов перед набором ставят л знак «крышечка» ( ). Следующий шаблон требует семь символов, не являющихся цифрами: ГО-93Ш Если вам хочется узнать побольше, обращайтесь в MSDN. Большинство задач по проверке корректности входных данных решается с применением RequiredFtetdValidator, CompareValtdator, RangeValidator и RegularExpressionValidator. Этих элементы управления поддерживают обработку полей самых разнообразных типов. Но что делать, если нужно выполнить особую проверку? В этом случае не обойтись без Custom Validator. Он применяется, когда посредством стандартных элементов управления задачу не решить. Например, если значение нужно сравнить не с константой или регулярным выражением, а со значением из базы данных. Далее приведен фрагмент кода из ValidatorTest.aspx (листинг 5-4), е котором создается CustomValidator: <ASP:CustomValidator ID="ValCustom"

runat="server"

ControlToValidate="txtCustom" OnServerValidate="CustomServerVal"

Display="Static" >

Enter "Hello". Case-Sensitive. </ASP:CustomValidator> В отличие от предыдущих примеров, в которых для открытия и закрытия элемента управления применялся единичный тэг, а сообщение об ошибке хранилось в атрибуте ErrorMessage, здесь сообщение об ошибке размещается между открывающим и закрывающим тэгами. Особой практической разницы между двумя способами определения сообщения об ошибке нет. Здесь CustomValidator содержит новый атрибут, OnSetverValidate, в котором указывается функция (в этом примере это CustomServerVal), выполняющаяся на стороне сервера и принимающая два аргумента. Вот текст этой функции: void CustomServerVal (object source, ServerValidateEventArgs args) { try


Web-формы {

163

if ( args.Value.Equals("Hello") )

Msg.Text="ServerValidation called and TRUE returned."; args, IsValid=true; } else Msg.Text="ServerValidation called and FALSE returned."; args. IsValid=false;

)

catch { Msg.Text="ServerValidation called and FALSE returned."; args.IsValid=false; I

В этом примере важны два свойства ServerValidateEventArgs: Value и fsValid. Value применяется для получения значения элемента управления, что полезно для выполнения пользовательской проверки, являющейся целью CustomServerVal. Value — это свойство «только для чтения». Функция CustomServerVal просто сравнивает значение Value со строкой «Hello». Если они равны и во время сравнения не инициируется исключение, функция устанавливает свойство isValid экземпляра ServerValidateEventArgs в True. Если же IsValid установлено в False, элемент управления CustomValidator сработает и отобразит сообщение об ошибке, указанное либо в атрибуте ErrorMessage, либо между открывающим и закрывающим тэгами элемента управления CustomValidator, Как и в других элементах управления проверки данных, CustomValidator может поручить часть проверки клиенту. Атрибут ClientValidationFunctton позволяет указать функцию, которая на стороне клиента выполняет проверку значения элемента управления, на который указывает атрибут ControlToValidate. В этом примере проверка на стороне клиента отсутствует, но реализовать ее можно. Например, так:


164

Глава 5

<script language="javascript"> function ClientValiclate(source, value) { if (value == "Hello") return true; else return false; } </script>

При выполнении проверки на стороне клиента важно осознавать, что, как правило, языки клиентской и серверной проверок разнятся. Это может стать источником определенных ошибок. Например, чувствительна ли к регистру проверка строки как на сервере, так и на клиенте? Чтобы ответить на этот вопрос, нужно знать оба языка программирования.

Несколько элементов управления, проверяющих одно поле Если открыть страницу ValidatorTest.aspx (листинг 5-1) в браузере и щелкнуть кнопку Validate, вы увидите страницу, показанную на рис. 5-8. giew

Favttrites

Teds

,] ,f Validator Test Page Range Validation (1-12)' Regular Expression Validation (nim-nn-nnnn): Custom Validation (It wants you to enter "Hello" WITHOOT THE QUOTES):

Рис. 5-8. Страница, которая открывается по щелчку кнопки Validate при незаполненных полях ввода


Web-формы

165

Обратите внимание на сообщение о корректности страницы «Page Valid». Но это не то, что нам нужно! Первое поле должно быть числом из диапазона 1 — 12, а второе —строкой, похожей на номер социального страхования, а значение последнего поля должно равняться строке «Hello». Однако оказывается, что ни один из проверочных элементов управления, кроме RequiredFieidValidator, не выполняет проверку «пустых» значений. Должен же быть какой-то выход. Первое решение — использование элемента управления RequiredFieldValidator. В листинге 5-5 показана модифицированная версия ValidatorTestRequired.aspx файла ValidatorTest.aspx. Здесь к каждому полю, контролируемому другим элементом проверки корректности, добавляется элемент управления RequiredFieldValidator. <html> <script 1апдиаде="С#" runat=server> void Validate_Click(Ob]ect sender, EventArgs E) i

if ( Page.IsValid ) { Msg.Text="Page Valid";

void CustomServerVal (object source, ServerValidateEventArgs args) { try

{

if ( args. Value. Equals("Hello") )

Msg.Text="ServerValidation called and TRUE returned."; args. IsValid=true; } else Msg.Text="ServerValidation called and FALSE returned. args. IsValid=false; catch


166

Глава 5

Msg.Text="$erverValidation called and FALSE returned."; args. IsValid=false; </script>

<body> <form runat=server> <center> <h3>

<font face="Verdana" color=blue> Validator Test Page - Required Entry </font> </h3> <table> <tr> <td> Range Validation (1-12): </td> <td> <input id="Range" type="text" runat=server size=10 /> </td> <td> <ASP: RangeValidator ID="ValRange" Con trolToValidate= "Range" Display="Dynamic" Type="Integer" HinimumValue="1" MaximumValue="12" ErrorMessage="Out of Range" runat=server /> <ASP: RequiredFieldValidator ControlToValidate=" Range" Display="Dynamic" ErrorMessage="Must enter a value." runat=server /> </td> </tr> <tr> <td> Regular Expression Validation (nnn-nn-nnnn):


Web-формы

167

</td> <td> <input id="RegEx" type="text" runat=server size=11 /> </td>

<td>

<ASP: RegularExpressionValidator ID="ValRegEx" ControlToValidate="RegEx" runat="SERVER" Display="0ynamic" ErrorHessage= "Enter a valid U.S. SSN (nnn-nnnnnn). " ValidationExpression= <ASP: RequiredFieldValidator ControlToValidate="RegEx" Display="Dynamic" ErrorMessage="Must enter a value. runat=server /> </td> </tr> <tr> <td> Custom Validation (It wants you to enter "Hello" WITHOUT THE QUOTES): </td> <td> <input type="text" id="txtCustom" runat=server size=11 /> </td> <td> <ASP: CustomValidator ID="ValCustom" runat="server" Cont rolToValidate="txtCustom" OnServerValidate="CustomServerVal Display="Dynamic" > Enter "Hello". Case-Sensitive. </ASP:CustomValidator> <ASP: RequiredFieldValidator


Глава 5

168

ControlToValidate="txtCustom" Display="Dynamic" ErrorMessage="Hust enter a value." runat=server /> </td> </tr> <tr> <td colspan=3 align="center"> <asp:button text="Validate" OnClick="Validate_CHck" runat=server> </asp:button> <P> <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server /> </td> </tr> </table> </center> </form> </body> </ntml>

Листинг 5-5. Страница Validator FestRequired.aspx, на которой требуется заполнять все поля корректными данными Один из атрибутов, которых мы до сих пор не рассматривали, это Display. Он может принимать одно из трех значений —None, Static или Dynamic. Если установить Display в None, сообщение об ошибке не выводится. Если атрибут равен Static, при выводе сообщения об ошибке структура страницы не изменяется, так как содержимое элемента проверки физически является частью страницы, и для него всегда отводится место. При установке атрибута Display в Dynamic сообщения не занимают место на странице, пока не «срабатывают». В листинге 5-5 я устанавливаю атрибут Display всех элементов управления равным Dynamic. У этого значения есть неприятный побочный эффект — изменение внешнего вида страницы при срабатывании проверочных элементов управления. Если определить значение равным Static, этого не произойдет. Например, после изменения значения этого атрибу-


Web-формы

169

та на Static для элемента RangeValidator u отправки страницы с незаполненным полем Range я получил страницу, которая показана на рис. 5-9. Sle

fdlt

Validator Test Page - Required Entry Range Validation (1-12):

enter a value.

Regular Expression Validation (nnn-nn-nruin): Custom Validation (It wants you to enter "Hello" WITHOUT THE QUOTES):

Must enter a value j

,, **»* "* a ™ue"

Рис. 5-9. Результат установки атрибута Display в Static в элементе управления, проверяющем содержимое поля С первым элементом проверки (не сработавшим) связано сообщение об ошибке «Out of Range» (выход за пределы разрешенного диапазона). На рис. 5-9 видно свободное место между полем ввода и началом строки «Must enter a value», где размещается невидимая строка «Out of Range». Имея исходный HTML-код этой страницы, мы можем посмотреть, чем руководствовался браузер при отображении страницы. Далее приведен возвращенный браузеру HTML-код для строки таблицы, в которой находится элемент проверки диапазона. (Для облегчения чтения HTMLтекст отформатирован.) <tr> <td> Range Validation (1-12): </td> <td> <input

name="Range"

id="Range" type="text" size="10" />


Глава 5

170 </td> <td> <span id="ValRange" controltovalidate="Range" errormessage="0ut of Range"

type="Integer" evaluationfunction="RangeValidatorEvaluateIsValid" maximijmvalue="12" minimumvalue="1" style="color:Hed;visibility:hidden;"> Out of Range </span> <span id="_ct!1" controltovalidate="Range" errarmessage="Must enter a value." display="Dynamic" evaluationfunction="Requ:LredFieldValidatorEvaluateIsValicT initialvalue="" style="color:Red;display:none;"> Must enter a value. </span> </td> </tr> Как видите, в HTML-коде есть невидимая строка «Out of Range:». Подводя итог, можно сказать, что чаще всего при наличии на странице не более двух элементов проверки рекомендуется присваивать атрибуту Display значение Static.

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


Web-формы

171

В ASP. NET есть специальный элемент управления — ValidationSummary. Он обрабатывает все сообщения об ошибках от всех элементов проверки и выводит их в одном месте. В листинге 5-6 показано, как этот элемент управления применяется на странице ValidatorTestSummary.aspx. <html> <script language="C#" runat=server> void Validate Click(0bject sender, EventArgs E) !

if ( Page.IsValid ) { Hsg.Text="Page Valid";

void CustomServerVal (object source, ServerValidateEventArgs args) { try I if ( args. Value. Equals("Hello") ) Msg.Text="ServerValidation called and TRUE returned."; args. IsValid=true; } else Msg.Text="ServerValidation called and FALSE returned. args. IsValid=false; catch Msg.Text="ServerValidation called and FALSE returned. args.IsValid=false; } J </script> <body>


172

Глава 5 <form runat=server> <center> <h3> <font face="Verdana" color=blue> Validator Test Page - Summary </font> </h3> <table> <tr> <td> Range Validation (1-12): </td> <td> <input id="Range" type="text" runat=server size=10 /> </td> <td> <ASP:RangeValidator ID="ValRange" CentrolToValidate="Range" Display="None" Type="Integer" MinimumValue^'l" HaximumValue="12" ErrorMessage="Range" runat=server /> <ASP:RequiredFieldValidator ControlToValidate="Range" Display="None" ErrorMessage="Range" runat=server /> </td> </tr> <tr> <td> Regular Expression Validation (nnn-nn-nnnn): </td> <td> <input id="RegEx" type="text" runat=server size=11 /> </td> <td>


Web-формы

173

<ASP:RegularExpressionValidator ID="ValRegEx" ControlToValidate="RegEx" runat="SERVER" Display="None" ErrorMessage="Regular Expression" ValidationExpression= "[0-9]{3}-[0-9]{2КО-9]<4Г /> <ASP:RequiredFieldvalidator ControlToValidate="RegEx" Display="None" ErrorMessage="Regular Expression" runat=server /> </td> </tr> <tr> <td> Custom Validation (It wants you to enter "Hello" WITHOUT THE QUOTES): </td> <td> <input type="text" id="txtCustom" runat=server size=11 /> </td> <td> <ASP:CustomValidator ID="ValCustom" runat="server" ControlToValidate="txtCustom" OnServerValidate="CustomServerVal Display="None" > Custom

</ASP:CustomValidator> <ASP:RequiredFieldvalidator ControlToValidate="txtCustom" Display="None" EгrorMessage="Custom" runat=server /> </td> </tr> <tr> <td colspan=3 align="center"> <asp:button text="Validate"


174

Глава 5 OnClick="Validate_Click" runat=server> </asp:button> <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana"

Font-Size="10" runat=server /> <asp:ValidationSummary id="valSum" DisplayMode="BulletList" ShowSummary="true" runat="server" HeaderText= " Y o u must enter a value in the following fields:" Font-Name="Verdana" Font-Size="12"/> </td> </tr> </table> </center> </form> </body> </html> Листинг 5-6. Страница ValidatorTestSummary.aspx, на которой демонстрируется применение элемента управления ValidationSummary На рис. 5-10 приведена страница, возвращенная после щелчка кнопки Validate при незаполненных полях.


175

Web-формы

i '•;-} tep.//loca«VKt^a«dalorWst^BkdelorTes[5ummary.aspl

jjl ;,!#&.

.rt,».

Validator Test Page - Summary Range Validation (1-12) Regular EHpressioc Vatdatioa (nm-nn-Eran)Custom Validation (It wants you to enter "Hello" ВДТНОЩ THE QUOTES)

r'ou must enr&i' :•> value Ir the foticv.'ing fields:

Рис. 5-10. Отчет обо всех обнаруженных ошибках, отображаемый после отправки формы на странице ValidatorTestSummary.aspx с пустыми полями Самое важное изменение з листинге 5-6 —это элемент управления ValidationSummary, добавленный в конце страницы под кнопкой и надписью; <asp:ValidationSummary id="valSum" DisplayHode="Bullet List" ShowSummary="true" runat="server" HeaderText="You must enter a value in the following fields:" Font-Name="Verdana" Font-Size="12"/> Сейчас я познакомлю вас с атрибутами элемента управления ValidationSummary, назначение которых не очевидно из их названий. Атрибут DisplayMode указывает, как отображать сообщения об ошибках. Допустимые значения являются частью перечислен ия ValidationSummaryDisplayMode: • BulletList —сообщения об ошибке выводятся в виде маркированного списка; • List — сообщения об ошибке выводятся в виде списка; •

SingleParagraph — все сообщения об ошибках выводятся в одном абзаце.


176

Глава 5

Атрибут ShowSummary способен принимать значения True или False и определяет, должен ли отчет об ошибках выводиться в HTML. В этом примере не показан атрибут ShowMessageBox, который применяется для отображения отчета в информационном окне. Он также принимает значения True или False. В атрибуте HeaderText указывается текст заголовка отчета об ошибках. Другие отличия ValidatorTestSummary.aspx (листинг 5-6) от ValidatorTestRequired.aspx (листинг 5-5) заключаются б том, что атрибуты Display всех проверочных элементов управления установлены в None, а текст сообщений об ошибке содержит понятное пользователю название проверяемого элемента управления таким образом, что в сочетании со значением атрибута HeaderText сообщение становится осмысленным.

Сохранение информации о состоянии элементов управления в ASP.NET В «классическом» ASP-коде обработки формы для проверки корректности введенных данных обычно требуется писать много кода. Вторая задача, которая требует создавать объемный код, сохранение информации о состоянии элементов управления между отправками формы, HTTP не поддерживает состояния и не обеспечивает постоянных, длительных подключений. Каждое обращение к серверу рассматривается как новый запрос, и хотя в .NET Framework создается иллюзия сеансов, иллюзия все равно остается иллюзией. Нет никакого постоянного подключения, и по умолчанию сервер ничего не помнит о клиенте между отправками страницы. При создании форм для просмотра и редактирования информации в базе данных на «классических» ASP-страницах я обычно назначаю следующую последовательность действий для сохранения информации о состоянии. 1. Определить, отображается ли форма в первый раз. Если отображается, перейти к чтению базы данных, то есть к шагу 4. 2. Если форма выводится не в первый раз, проверить входные данные. Если они корректны, сохранить их и перенаправить пользователя на следующую страницу. Если входные данные неверны, перейти к шагу 3.


Web-формы

177

3. Сохранить введенные данные в локальных переменных, 4. Если форма отображается в первый раз, прочесть информацию из базы данных и сохранить ее в локальных переменных. 5. Отобразить форму. Во всех элементах управления, в которых это требуется, присвоить атрибутам value значения из соответствующих локальных переменных, значение которых определено на шаге 3 или 4. Таким образом, для решения этой задачи требуется выполнить огромный объем вычислений, и хотя здесь нет ничего сверхсложного, шанс ошибки увеличивается. Пользователи путаются, а разработчики ломают голову, выискивая и устраняя неполадки и «нестыковки» в таком коде. За примером далеко ходить не надо. В проекте, над которым я недавно работал, я неосторожно использовал две схемы именования элементов управления; согласно первой из них я использовал префикс, определяющий тип элемента управления (например, txtFirstName), а согласно второй применил только описательное имя (т. е. FirstName]. Выискивать несоответствия в наименованиях различных элементов управления оказалось задачей чрезвычайно неблагодарной. Пришлось проверить функции, определения того, в первый ли раз отображается форма или нет, функции, осуществляющие проверку данных и значения, а также значения по умолчанию для разных HTML-элементов управления. Как вы вскоре увидите, в ASP.NET сохранять информацию о состоянии существенно проще. В ASP.NET при отправке формы сохранение информации о состоянии элементов управления берет на себя .NET Framework. Для этого не требуется никакого дополнительного программирования или конфигурирования. После отправки и последующего отображения формы предыдущие значения автоматически становятся значениями по умолчанию. Например, в листинге 5-7 показано содержимое простой формы StateTest.aspx. <Х@ Import Namespace="System.Web.Security " %>

<%@ Page ClientTarget="Downlevel" %>

<html> <script language="VB" runat=server>

Sub ValidateBtn_OnClick(sender As Object, e As EventArgs) If (Page.IsValid) Then

Msg.Text = "Page is Valid!" Else

7 Зак. 422


178

Глава 5

Msg.Text = "Page is Invalid!" End If End Sub </script> <body> <form runat=server> <center> <h3> <font face="Verdana" color=blue>Control Test Page - State </font> </h3> <table> <tr> <td> Name: </td> <td> <input id="Name" type="text" runat=server size=30 /> </td> <td> <ASP:RequiredFieldValidator ControlToValidate="Name" Oisplay="Static" ErrorMessage="Please enter name." runat=server /> </td> </tr> <tr> <td> SSN: </td> <td> <input id="SSN" type="text" runat=server size=11 /> </td> <td> <ASP:RequiredFieldValidator ControlToValidate="$SN" Display="Dynamic" ErrorMessage= "Enter a valid U.S. SSN (nnn-nn-nnnn),


Web-формы

179 runat=server />

<ASP:RegularExpressionValidator ID="ValRegEx" ControlToValidate="SSN" runat="SERVER" Display="Dynamic" ErrorHessage= "Enter a valid U.S. SSN (nnn-nn-nnnn), ValidationExpression= "[0-9]{3>-[0-9]<2КО-9]{4Г /> </td> </tr> <tr> <td colspan=3 align="center"> <asp:button text="Validate" OnClick="ValidateBtn_OnClick" runat=server> </asp:button> <P> <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server /> </td> </tr> </table> </center> </form> </body> </html> Листинг 5-7. В файле StateTest.aspx демонстрируется сохранение введенных в форму значений между обращениями к серверу

Примечание В листинге 5-7 используется Visual Basic .NET/ а не С#. Общая структура не меняется, и хотя функция, вызываемая при нажатии кнопки Validate, называется ValidateBtn_OnClick, единственной причиной такой схемы именования является мое желание. Обработчики события в таком формате часто генерирует Visual Studio .NET, но в .NET Framework автоматически созданных имен придерживаться не обязательно.


180

Глава 5

В форме размещены проверочные элементы управления для полей Name (имя пользователя) и SSN (номер социального страхования). Если ввести имя (форма не придирается к именам) и неправильный номер социального страхования (в нашем случае — 111-111-111 вместо 111-11-1111), отображается страница, показанная на рис. 5-11, причем введенные ранее значения являются значениями по умолчанию при повторном отображении формы,

Control Test Page - State

п

Name: [Douglas Reilly SSN:

J111-111-111

; РИС. 5-11. Результат отправки StateTest.aspx при заполненном поле Name и неправильно введенном номере социального страхования

Примечание Для того чтобы в примере StateTest.aspx не выполнялась проверка на стороне клиента, я использовал пару «атрибут - - значение» ClientTarget=Downlevel, благодаря чему страница всегда обращается к серверу. Об этом свидетельствует сообщение в нижней части страницы о ее некорректности (Page is InValid) -- это сообщение создается методом, выполняющимся на стороне сервера.


Web-формы

181

Программное управление серверными элементами управления Кроме уже рассмотренных элементов управления, всем остальным стандартным элементам HTML соответствуют серверные элементы управления HTML и ASP, в том числе; •

Hyperlink (тэг <А>);

• Label, • DropDownList; •

ListBox;

Checkbox;

• RadioButton. Синтаксис серверных элементов управления HTML не отличается от синтаксиса стандартных элементов управления HTML за исключением пары мелких деталей: • если необходимо программно управлять элементами управления и их значениями, необходимо определить значение атрибута ID; • атрибут RunAt следует установить в Server. Чаще всего серверные элементы управления ASP создаются так же, как элементы управления HTML, но опять же с определенными исключениями. Во-первых, для создания всех серверных элементов ASP применяется тэг вида <&5Р:тип_элемента_уг>равления>. Кроме того, некоторые свойства элементов управления ASP больше похожи на свойства традиционных элементов управления в Visual Basic, чем в HTML. Яркий пример такого элемента управления ASP:TextBox. Первое, что бросается в глаза — название TextBox. В «мире» HTML это был бы тэг INPUT с атрибутом Туре, установленным в Text. При изучении его свойств параллели с Visual Basic становится еще очевиднее. Атрибут, имевший в HTML название Value, здесь называется Text.

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


182

Глава 5

решение оставить северным элементам управления HTML их «унаследованные» имена и в то же время создать иерархию элементов управлений, хорошо знакомую программистам на Visual Basic, переходяи_шм на ASP.NET. Я полагаю, это разумный компромисс. Кроме серверных элементов управления, имеющих аналоги в HTML, существует ряд серверных элементов управления ASP, которым ничего не соответствует в HTML. К ним относится уже рассмотренное нами семейство элементов проверки данных. Для облегчения разработки приложений, взаимодействующих с базами данных, применяется другое большое семейство элементов управления, рассказ о которых я отложу до главы 9. Некоторые элементы управления позволяют разработчику, применяющему ASP.NET, создать более функциональный пользовательский интерфейс. Простой пример — элемент управления LinkButton, то есть кнопка в виде гиперссылки. Элемент управления Calendar гораздо сложнее. До сих пор при разработке Web-приложений разработчикам приходится отказываться от очень полезного стандартного элемента управления Windows DateTimePicker. Конечно, для достижения того же эффекта можно использовать элемент управления ActiveX, выполняющийся на стороне клиента, но в большинстве случаев разработки Интернет-приложении этот подход себя не оправдывает. Задачу выбора даты решает серверный элемент управления ASP.NET Calendar, который преобразуется в «чистый» HTML-код. Атрибуты этого элемента управления слишком многочисленны, чтобы перечислять их здесь, кроме того, они хорошо описаны в документации MSDN. Код Calendar выглядит примерно так: <asp:Calendar id="Calendar1" runat="server" Width="277px" Height="188px" On SelectionChanged="Selection_Change"> <TodayDayStyle ForeColor="eOOOOCO" BorderStyle="Solid" BorderColor="Red">


Web-формы

183

</TodayDayStyle> </asp:Calendar>

Большинство атрибутов элемента управления Calendar, например Width, Height u ID, нет необходимости пояснять, В этом примере между открывающим и закрывающим тэгом <ASP:Calendar> размещается подчиненный тэг TodayDayStyle — он определяет, в каком виде отображается текущая дата. На сегодняшний день у этого подтэга имеется более 10 допустимых элементов, в том числе атрибуты BorderStyle и BorderFont, а также информация о шрифте. Использование атрибутов для указания множества элементов достаточно утомительно. К счастью, при использовании среды разработки Visual Studio .NET эти атрибуты устанавливаются в окне Properties, точно таком же, к какому привыкли программисты на Visual Basic. Кроме того, значения отдельных атрибутов можно изменять программно.

Файлы программной логики, или CodeBehindфайлы Все предыдущие примеры в основном создавались в редакторе Notepad (Блокнот), который, конечно же, ничего не знает о .NET Framework. Большинство следующих примеров (в том числе WebForml .aspx) создавались в Visual Studio .NET. Одно из важнейших отличий ASP от ASP.NET — местоположение кода. В ASP код должен находиться в ASP-файле или подключаться к нему. В ASP.NET исповедуется другой принцип. В WebForml .aspx весь программный код располагается в отдельном файле, который в соответствии с общепринятыми соглашениями называется WebForml .aspx.vb {так как это код на Visual Basic .NET). Это файл программной логики страницы, так называемый CodeBehind-файл. Если бы в примере вместо Visual Basic .NET использовался С#, файл получил бы название WebForml .aspx.cs. Возможность удобного разделения логики программы и представления очень важна в группах, где дизайнеры и разработчики работают в разных командах. В оставшейся части этого раздела описывается пример ControlShowAndTell. В нем используются четыре серверных элемента управления ASP.NET (Label, Calendar, LinkButton, TextBox}, управление которыми осуществляется программными методами. В


184

Глава 5

листинге 5-8 показано содержимое файла WebForml .aspx, назначение которого — представление страницы в браузере. Позже я познакомлю вас с содержимым CodeBehind-файла этой страницы. <Х@ Page Language="vb" AutoEventWireup=" false" Codebehind="WebForfn1 . aspx. vb" Inherits="ControlShowAndTell.WebForm1"S!> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTHL> <HEAD>

<title></title> <meta content="Hicrosoft Visual Studio. NET 7 . 0 " name="GENERATOR"> <meta content="Visual Basic 7 . 0 " name="CODE^LANGUAGE"> <meta content="JavaScript" name="vs_defaultClientScript"> <meta content="Internet Explorer 5.0" name="vs_ targetSctiema"> </HEAD> <body> <CENTER> <form id="Form1" onsubmit="FormSubmit" met hod= "post" runat="server"> <P> <asp: label id="Label2" runat="server" Width="175px" Height="35px" BackColor="ftFFCOCO" BorderStyle="Dotted" Font-Size="18pt" Font-Bolb="True"> Other Controls . </asp: label> <asp: calendar id="Calendar1" runat="server" Widtfi="277px" Height="188px" OnSelectionChanged="Selection_Ctiange"> <TodayDayStyle ForeColor="#OOOOCO" BorderStyle="Solid" BorderColor="fled"> </TodayDayStyle>


Web-формы

185 </asp:calendar> <asp:linkbutton id="LinkButton1" runat="server" Width="81px" Height="19px"> LinkButton </asp:linkbutton>

<asp:textbox id="TextBox1" runat="server"> </asp:textbox> </form> </CENTER> </body> </HTML> Листинг 5-8. WebFormI. aspx — файл представления страницы, созданный Visual Studio .NET В листинге 5-9 (файл WebFormI. aspx.vb) реализована программная логика на Visual Basic. Файл WebFormI .aspx.vb связан со страницей представления WebFormI .aspx. Public Class WebFormI Inherits System. Web. UI, Page Protected WithEvents Calendar"! As System. Web. Ul.WebControls. Calendar Protected WithEvents LinkButtonl As System. Web. UI, WebControls. LinkButton Protected WithEvents Label2 As System. Web. Ul.WebControls. Label Protected WithEvents Forml As System. Web, UI. HtmlControls. HtmlForm Protected WithEvents TextBoxl As System. Web. Ul.WebControls. TextBox ttRegion " Web Form Designer Generated Code " 'Этот вызов необходим для Web Form Designer. <Systera. Diagnostics. DebuggerStepTh rough (}> _ Private Sub InitializeComponent() End Sub


186

Глава 5

Private Sub Page_Iriit(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: Вызов этого метода необходим 'для Web Form Designer 'He изменяйте его средствами редактора кода. InitializeComponentO End Sub #End Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Разместите здесь пользовательский код 'инициализации страницы If Page. IsPostBackO = False Then Calendar!.BackColor = System.Drawing.Color.BlanchedAlmond Calendar!.ForeColor = System.Drawing.Color.Red Calendar!.TodaysDate = "7/24/2001" LinkButtonl.Enabled = False TextBoxf.Text = "Hello" End If End Sub Sub Selection_Change(ByVal sender As Object, ByVal e As EventArgs) Dim s As String s = Calendar!.SelectedDate.ToStringO TextBoxl.Text = s.SubstringfO, s.IndexOf(" ")) End Sub 'Selection_Change End Class Листинг 5-9. WebForml.aspx.vb — CodeBehind-файл для WebForm 1 .aspx, демонстрирующий программное управление элементами управления; создан на Visual Basic .NET Первое отличие листинга 5-8 от предыдущих примеров заключается в директиве Page: <Х§ Page Language="vb" АиtoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="ControlShowAndTell.WebForm1"Jf>


Web-формы

187

Атрибуты AutoEventWireup и Codebehind характерны для Visual Studio .NET. Первый из них практически всегда равен False. Если установить этот атрибут в True или вообще опустить, обработчик события с именем Pagejnit будет автоматически связываться с событием страницы Init. Использование AutoEventWireup казалось уместным в момент его создания, но на практике этот атрибут иногда порождает путаницу. Многие разработчики, пользовавшиеся ранними бета-версиями ASP.NET, жаловались, что при случайном подключении событий вручную совместно с определенным атрибутом AutoEventWireup выполнялись два вызова события вместо требуемого одного. Visual Studio .NET применпет Codebehind на этапе проектирования, а в .NET Framework этот атрибут игнорируется. В проектах, в которых Visual Studio .NET не используется, часто для указания выполняющегося на этой странице кода применяют атрибут 5гс директивы Page. WebForml.aspx содержит ряд тэгов <МЕТА>, вставленных Visual Studio .NET. Эти HTML-элементы содержат невидимую для пользователя информацию о документе, полезную как серверу, так и клиенту. Поисковые машины часто учитывают информацию з тэгах <МЕТА> при индексации страниц. Остальной код листинга 5-8 совпадает с предыдущими примерами: в нем определяются серверные элементы ASP.NET. Заголовочная надпись «приукрашена» прерывистой границей и фоновым цветом, но остальные элементы управления созданы с применением минимума атрибутов. В начале листинга 5-9 (файл WebForml .aspx.vb) показано открытие объявления класса: Public Class WebForml Inherits System.Web.UI.Page Protected WithEvents Calendar! As System.Web.Ul.WebControls. Calendar

Protected WithEvents LinkButtonl As System.Web.UI. WebControls.LinkButton

Protected WithEvents Label2 As System.Web.Ul.WebControls. Label

Protected WithEvents Forml As System.Web.UI.HtmlControls. HtmlForm Protected WithEvents TextBoxl As System.Web.Ul.WebControls. TextBox


188

Глава 5

Открытый класс WebForml описан и объявлен как наследник System.Web.UI.P3ge. (Самую подробную информацию о последнем вы найдете в MSDN.) Затем объявляются четыре элемента управления, а именно tabel2, CalendarT, LinkButtonl и TextBoxl. Все они объявлены серверными элементами управления в WebForml.aspx (листинг 5-8), а здесь их применение вызвано необходимостью управлять ими из программы. Для объявления этих элементов управления используется ключевое слово Visual Basic .NET Wtthevents, то есть эти объекты реагируют на события, инициируемые их экземплярами, приписанными к переменным. Все серверные элементы управления ASP расположены в пространстве имен System. Web. Uf. WebControls. В листинге 5-9 сразу за переменными-экземплярами располагается следующая загадочная строка: SRegion " Web Form Designer Generated Code "

Код между этой строкой и строкой #EndRegion, расположенной ниже, по умолчанию скрыт при редактировании кода в Visual Studio .NET. Программисты на Visual C + + , пользующиеся MFC и ATL, хорошо знакомь! с подобным сгенерированным средой разработки кодом, но программисты на Visual Basic могут его и не знать.

Примечание Программисты на Microsoft Visual C + + безусловно знакомы с фрагментами кода, поддержку которых берет на себе среда разработки. Со временем многие даже научились вручную менять эти строки, хотя, конечно же, это не рекомендуется. В программах на Visual Basic нет кода, доступного для программиста, но на самом деле управляемого средой разработки. В языке Visual Basic в код вставлялись определенные «секретные» строки, но разработчики их не видели. В модели .NET во всех поддерживаемых Visual Studio .NET языках отсутствуют «секретные ингредиенты». Видны все строки кода — даже не предназначенные для изменения программистом. Сейчас просто не обращайте внимания на фрагменты, помеченные строкой «Web Form Designer Generated Code». Автоматичес-


Web-формы

189

ки созданный код в листинге 5-9 достаточно прост, но в других примерах он гораздо сложнее. Так как Visual Studio .NET зарезервировала событие Pagejnlt для собственных нужд, нам придется найти другое место для кода, выполняющегося в начале обработки. Конечно же, такое место есть — это событие Page__ioad. Свойство IsPostBack класса Page позволяет определить, в первый ли раз отображается форма.

Жизненный цикл Web-формы в ASP.NET Жизненный цикл Web-формы в ASP.NET состоит из пяти основных этапов: • Pagejnit — при этом событии каркас страницы ASP.NET восстанавливает свойства элементов управления и отправленные данные, то есть данные, введенные пользователем до отправки формы; Ш Page_ioad— при этом событии выполняются начальные операции {при отображении страницы в первый раз) либо восстанавливаются данные в элементах управления (если страница отображается после обращения к серве-

ру); Ш Проверка данных (Validation) —метод Validate серверных элементов управления ASP.NET вызывается для проверки данных в элементах управления; Ш Обработка прочих событий — различные элементы управления инициируют самые разнообразные события. Например, элемент управления Calendar инициирует событие SelectionChanged (об этом чуть позже). Нет никакой гарантии, что события будут вызываться в каком-то определенном порядке за исключением того, что отнесенные в кэш события элементов управления (как указано в свойстве AutoPostBack] всегда обрабатываются до события отправки. Если страница содержит элементы управления проверки данных, следует проверять свойство IsValid страницы и конкретных элементов управления, чтобы выяснить, успешно ли прошла проверка;


190

Глава 5

PageJUnload— это событие вызывается при окончании формирования изображения страницы. Именно на этом этапе следует освобождать все занятые ресурсы, особенно такие «тяжеловесные», как описатели файлов и подключения к базам данных. Одного выхода ресурсов заграницы области видимости недостаточно, особенно на сильно загруженном сервере, где задержка запуска «сбора мусора» ухудшает производительность. После загрузки страницы возможны два сценария развития событий в зависимости от того, отображается страница s первый раз или нет. За обработку двух возможных вариантов отвечает обработчик события Page_ioad: Private Sub Page_Load(ElyVal sender As System.Object, ByVal e As System.EventArgs) Handles HyBase.Load 'Разместите здесь пользовательский код инициализации страницы If Page.IsPostBack = False Then Calendar!.BackColor = System.Drawing.Color.BlanchedAlnmnd Calendar"!. ForeColor = System. Drawing.Color. Red Calendar"!.TodaysDate = "7/24/2001" LinkButtonl,Enabled = False TextBoxl.Text = "Hello" End If End Sub В этом примере обработка выполняется, только когда свойство Page.lsPostback равно False, то есть страница отображается в первый раз и форма не заполнена. В этом случае страница программно изменяет некоторые свойства элемента управления Calendar, в том числе свойство Enabled кнопки-ссылки и свойство Text текстового поля. В элементе управления Calendar устанавливаются свойства BackColor, Foi'eColor и TodaysDate. С помощью свойства TodaysDate устанавливается текущая дата для элемента управления, возможно, отличающаяся от системной даты на сервере или клиенте. Эти простые примеры определения свойств — только вершина айсберга по сравнению со всеми возможностями программного управления компонентами. На рис. 5-12 показан результат обработки ControlShowAndTell при запросе страницы WebForml .aspx.


191

Web-формы

J7MJ20Q1

Рис. 5-12. Страница WebForml.aspx с датой (свойство TodaysDate) 4 июля, определенной в коде В коде WebForml .aspx.vb {листинг 5-9} кнопка-ссылка заблокирована — это видно на рис. 5-12. Однако, свойство Text поля ввода, расположенного прямо под кнопкой в методе Page_Load, установлено равным «Hello». На рис. 5-12 в поле ввода содержится текст «7/4/2001» — выделенная дата. Как это произошло? Дело 8 коде метода Selection_Change (листинг 5-9). Sub Selection_Change(ByVal sender As Object, _ ByVal e As EventArgs) Dim s As String s = Calendar!.SelectedOate.ToStringO TextBoxl.Text = s.Substring(0, s.IndexOf(" ")) End Sub 'Selection_Change Атрибут OnSelectionChange элемента управления Calendar установлен в Selection_Change. Этот метод вызывается на сервере при каждом изменении выделения. Он просто меняет свойство поля ввода Text и записывает туда дату, указанную в элементе управления Calendar.


192

Глава 5

Строки, даты и многофункциональный каркас На примере ControlShowAndTell демонстрируется богатство объектной модели среды .NET. Обратите внимание, я присвоил промежуточной переменной 5 результат, возвращенный методом ToString свойства SelectedDate. Поначалу возникли определенные трудности, Когда я выбирал дату 4 июля 2001 года, вместо текста «7/4/2001», в поле ввода отображалось «7/4/2001 12:00:00 AM». Чтобы получить только дату, без времени, мне пришлось воспользоваться методом Substring. Вывести дату можно и другим способом: вместо программирования метода Selection_Change в Calendar! использовать аргумент Sender, передаваемый обработчику события: Sub Selection_Change(ByVal sender As Object, ByVal e As EventArgs) Dim с As Calendar Try с = CTypefsender, Calendar) c. SelectedDate-ToShortDateStringO TextBoxl.Text = c.SelectedDate.ToShortDateStringO Catch End Try End Sub 'Selection_Change Вначале аргумент sender приводится к объекту Calendar. Этот код помещен блок обработки исключений Try/Catch, так как при использовании этого обработчика для обработки других событий, не связанных с календарем, произойдет ошибка. После создания объекта Calendar из строки с полной датой исключается время посредством метода ToShortDate. В методе Catch никакой обработки не происходит, ведь этот обработчик предназначен только для объекта Calendar, и если объект не относится (или не приводится) к этому типу, ничего делать не надо. При создании проекта ASP.NET Web Application в Visual Studio .NET создается множество вспомогательных файлов. На рис. 5-13 показаны файлы, созданные для примера ControlShowAndTell.


Web-формы

193

ЗЫп Cont-DiBhamAridlet.ai Out -aShoMftidTell. *W01 Ct*i: i ^Е'щлЛпгГ'еН. >r нЬ со

Рис. 5-13. Файлы, созданные Visual Studio .NET для примера ControlShowAndTell В листинге 5-10 показано содержимое файла Global.asax.vb для примера ControlShowAndTell. Этот файл содержит элементы кода, в определенной степени похожие на обработчики событий из Global.asa з «классическом» ASP. Imports System.Web Imports System.Web.SessionState Public Class Global Inherits System.Web.HttpApplication ftRegion " Component Designer Generated Code " Public Sub New() MyBase.NewC) 'Этот вызов необходим для Component Designer. InitializeComponentO 'Все операции по инициализации выполняются 'поспе вызова InitializeComponentO End Sub 'Необходимо для Component Designer Private components As System.ComponentModel.Container


194

Глава 5

'ПРИМЕЧАНИЕ: Эта процедура необходима для Component Designer 'Ее можно модифицировать средствами Component Designer. 'Не модифицируйте ее средствами редактора кода, <System.Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponentO components = New System.ComponentModel.ContainerO

End Sub

ttEnd Region Sub Application^BeginRequestfByVal sender As Object, _ ByVal e As EventArgs) 1 Срабатывает в начале каждого запроса

End Sub

Sub Application_AuthenticateRequest(ByVal sender As Object, _ ByVal e As EventArgs) ' Срабатывает при попытке аутентифицировать доступ

End Sub Sub Application_Error(ByVal sender As Object, _ ByVal e As EventArgs) ' Срабатывает при возникновении ошибки

End Sub End Class Листинг 5-Ю. Файл Global.asax.vb, созданный средой Visual Studio .NET Global.asax.vb также содержит фрагмент кода, созданный средой разработки. При просмотре в Visual Studio .NET он по умолчанию скрыт. При компиляции или запуске проекта Visual Studio .NET создается каталог bin, о котором располагается файл ControlShowAndTell.dll, содержащий скомпилированный вариант WebPage! .aspx.vb, а также отладочный файл. Примечание Утилита Depends.exe, поставляемая в составе Visual Studio 6.0, позволяет выяснить, что ControlShowAndTell.dll зависит лишь от одной DLLбиблиотеки -- Mscoree.dll, в которой содержится большинство функций .NET Framework.


Web-формы

195

Заключение Вы изучили основные «строительные блоки» Web-форм и узнали, как их использовать для перенесения RAD-методов на север, Несмотря на сравнительную простоту примеров, мощь ASP.NET Framework вполне очевидна. При создании приложений удается «вынести за скобки» всю «черновую работу», которую приходилось выполнять на каждой разрабатываемой странице, и уделить больше внимания особенностям ваших страниц. Для организаций, в которых конструирование представления страницы и разработка программной логики осуществляются раздельно, возможность командам сконцентрироваться на своей части работы оказывается очень кстати. Разработчики интерфейса получают прекрасный набор инструментов для создания пользовательского интерфейса, о котором раньше не приходилось и мечтать. А разработчики программной «начинки» — возможность независимо разрабатывать основные функции, требуемые логикой приложения. Что делать, если описанных в этой главе элементов управления не хватает для воплощения задуманного? Об элементах управления, поддерживающих взаимодействие с базами данных, я расскажу в главе 9, но что, если нужно хитрым образом чуть изменить стандартное поведение компонента? Разработке собственных компонентов посвящена глава 6. Несмотря на то, что почти во всех приведенных примерах использовалась только .NET Framework, при разработке собственных элементов управления используются особенности двух основных языков программирования в .NET — Visual Basic .NET и С#.


Глава 6

Создание

Одна из причин беспрецедентного (и немного даже неожиданного) успеха Microsoft Visual Basic — возможность его расширения за счет добавления новых компонентов. Несмотря на то, что а прежних версиях языка (до Visual Basic .NET) не были реализованы в полной мере преимущества объектно-ориентированного программирования, Visual Basic сразу же занял лидирующую по* зицию, поскольку не только Microsoft, но и другие компании получили возможность разрабатывать свои и использовать уже готовые компоненты. В начале этой главы я познакомлю вас с тем, как сегодня обстоят дела с уже созданными компонентами. Далее вы узнаете, как в ASP.NET решаются многие проблемы, с которыми сталкиваются разработчики СОМ-компонентов в среде Active Server Pages (ASP). Я расскажу о классах элементов управления в ASP.NET и о жизненном цикле элементов управления ASP.NET. В заключение речь пойдет о внешней и «изнаночной» сторонах создания и функционирования пользовательских и нестандартных элементов управления в ASP.NET.

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


Создание компонентов ASP.NET

197

объекта, хотя обычно их «объектности» вполне достаточно для практических целей. Конечно, при разработке первых VBX-элементов и даже нынешних СОМ-элементоз не существовало удобного механизма наследования. Кроме того, полностью не решены проблемы с совместимостью версий. Идея о неизменном интерфейсе, который совместим со всеми будущими версиями компонента, безусловно, хороша, однако в жизни подчас очень трудно создать новую реализацию компонента, которая в точности повторяет поведение предыдущей версии. Однажды для создания шифрованных RTF-файлов я воспользовался приобретенным у стороннего поставщика элементом управления для обработки текста. Две первых версии этого элемента прекрасно работали с программой шифрования, которая обрабатывала текст фрагментами по 2048 символов (это обуславливалось ограничениями на использование памяти в 16-разрядной Microsoft Windows). V меня не было особых проблем, связанных с этим ограничением, поскольку от элемента управления я получал текст с символами конца строки в месте каждого разрыва строки (обозначаемого RTF-тегом {para}}. Все шло хорошо, пока я не перешел на третью версию этого элемента управления. И тут вдруг все рухнуло! Сейчас-то понятно, из-за чего возникла проблема. В соглашении об интерфейсе между разработчиком и конечным пользователем элемента управления четко описано, как передавать и получать текст, как вычислять длину текста в элементе управления, как выделять определенные символы и многие другие детали. Однако в описании ни слова не сказано о том, как обрабатываются физические разрывы строки. Не ожидая подвоха, я полагал, что элемент управления поведет себя так же, как и в первой версии. На самом деле в RTF-тексте физические разрывы строк не нужны, и третья версия элемента управления перестала вставлять символ конца строки. То есть теперь фрагмент мог состоять, например, из 4000—8000 символов (включая управляющие символы и тому подобное) без какого бы то ни было физического разрыва строки. С таким огромным куском текста не справлялась программа шифрования, которая рассчитана на работу с небольшими фрагментами. Новый элемент управления текстом должным образом обеспечивал тот же самый интерфейс, и тем не менее он нарушал ра-


198

Глава 6

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

разработка и отладка СОМ-компонентов — очень трудоемкий процесс.

Первую проблему, связанную с привязкой к версии, .NET Framework решает, позволяя хранить на компьютере разные версии компонентов. Теперь приложения могут запросить нужную им версию, так что старые приложения работают с прежней версией компонента, и с ними ничего не случится, если другое приложение, размещенное на том же компьютере, попросит более новую версию того же компонента. Вторая проблема всегда усложняла процесс развертывания и настройки СОМ-компонентов. Компоненты, созданные для .NET Framework, описывают себя сами, не полагаясь на реестр. Благодаря отсутствию зависимости от реестра облегчается развертывание компонентов, поскольку теперь достаточно просто скопировать их в нужное место. Что касается третьей проблемы, то поначалу, когда СОМ-компоненты использовались главным образом в настольных приложениях, ее практически не было. Ведь останов и повторный запуск настольного приложения и даже перезагрузка системы не считались, откровенно говоря, катастрофой. Правда, кому-то приходилось обходить все машины и выполнять обновление на каждой, но, вообще говоря, эта проблема как-то решалась. Однако по мере роста числа СОМ-компонентов, применяемых в серверных приложениях и тем более на Web-серверах, оказалось, что трудно найти время, когда приложение не действует, чтобы ус-


Создание компонентов ASP.NET

199

тановить новую версию компонента. Многие Web-узлы должны работать круглосуточно без выходных, и времени на обслуживание просто нет. К счастью, и для этой задачи есть решения, не лишенные, правда, недостатков. Допустим, я работаю с компьютерным кластером из четырех Web-cepsepos. При появлении новой версии СОМ-компонента сначала следует разобраться, чем отличается поведение новой и прежней версии, и только после этого составлять план дальнейший действий. Если разработчики нового компонента заявляют, что он совместим с существующим СОМ-компонентом и изменения связаны только с исправлением найденных ошибок, то все, что требуется, — это вывести сервер из кластера, остановить соответствующие службы [как правило, это IIS и Component Services (Службы компонентов)] и установить новый компонент. Затем компьютер обратно подключается к кластеру, и настраивается следующий сервер. А что если обновление включает не только исправление ошибки, но и добавление новых функций? Тогда оно существенно усложняется. Если новые функции требуют изменить ASP-код, вызывающий этот компонент, мне придется сначала пройти по всем серверам и заменить сам компонент, а затем пройти еще раз, чтобы на каждом сервере внести изменения в ASP-код. Но и в этой схеме возможны подводные камни. Допустим, один запрос к Web-серверу попадает на компьютер, который уже настроен на новые функции компонента, а следующий запрос, учитывающий новую конфигурацию компонента, попадает на другой сервер, в котором ASP-код еще не исправлен. Неполадка практически гарантирована. Эта лазейка для неприятностей не очень страшна на кластере из четырех серверов, но если у вас кластер больших размеров, головная боль гарантирована. ASP.NET в сочетании с некоторыми другими службами, являющимися частью .NET Framework, позволяет выполнить этот переход более гладко. (Найдите в MSDN информацию о Microsoft Application Center — службе, которая помогает выполнять подобные обновления.) Что касается самих компонентов, то новые компоненты ASP.NET копируются поверх прежних версий, и пользователи, подключенные к старой версии, могут не беспокоиться, а следующие запросы получат уже обновленные компоненты. Ко-


200

Глава 6

нечно, и тут есть проблемы, но возможность копирования нового компонента без остановки таких служб, как IIS и Component Services (Службы компонентов), уже можно считать огромным шагом вперед. И, наконец, четвертая проблема, связанная с трудностью процесса разработки и отладки СОМ-компонентов. Хотя разработка СОМ-компонентов в Visual Basic существенно проще, чем в С+ +, она no-прежнему остается весьма сложной процедурой — гораздо более сложной, чем следовало бы. Отладка СОМ-компонентов, вызываемых ASP-страницей, хотя и возможна, но очень сильно отличается от стандартной отладки ASP-сценариев. Как вы увидите дальше, применяя компоненты ASP.NET, удается решить все перечисленные и даже некоторые другие проблемы. Но сначала давайте разберем, из чего «сделаны» компоненты в ASP.NET.

Классы элементов управления ASP.NET Все сущности в .NET Framework являются объектами. Ну ладно, немного преувеличил — не совсем все, так как типы значений, такие, как целые и структуры, по умолчанию объектами не являются. Однако вас не должен удивлять тот факт, что создаваемые вами компоненты будут так или иначе производными от класса Object. На рис. 6-1 показана иерархия классов, которая является основой любого компонента. Пространство имен System.Web.UI содержит класс System.Web.Ul,Control, которому наследуют все серверные элементы управления. От System.Web.UI отпочковываются два наиболее важных пространства имен — System.Web.UI.WebControts и System.Web.UI.HtmlControls. Пространство имен HtmlControls включает элементы управления, которые напрямую отображаются в стандартные серверные элементы управления HTML (создаваемые с помощью стандартного синтаксиса HTML, с добавлением пары «атрибут — значение» Run At=Server). Пространство имен WebControls включает все элементы управления ASP.NET, тэги которых в ASPX-фаилах содержат приставку ASP;, например ASP-.TextBox.


Создание компонентов ASP.NET Svstem Qbiflct

1 O V u l C I r T OUIti^l

'

System. Web. Ul i—T^mplateOontrol

201

.-

' i ;*

Uftrt Л i! Щ tm »!•••< Н»,ч*т*ш, ,, .„mmi

Control

I 1

Содержит серверные Web -элементы управления

Содержит серверные HTML-элементы управления System.Web.Ul.WebControls System.Web.UI.HtmlContrals

—jUSBrGontroi Легенда

Literal Control DalaBotindLiteralControlj— Рис. 6-1.

Абстрактный класс

Иерархия классов серверных элементов управления

Примечание Если вы полагаете, что мир станет счастливее, если в эту иерархию добавить классы элементов управления Windows-форм, я хочу еще раз напомнить вам, что, увы, мир разработчиков далек от совершенства. Достаточно один раз взглянуть на класс System. Windows.Forms.Control, чтобы тут же понять, насколько непохожи друг на друга две среды элементов управления —Web и Windows. Например, на элементы управления Web не распространяются такие понятия, как z-порядок (z-order) или описатели окон (window handles), в то же время на элементы управления Windows не распространяются такие понятия, как рендеринг (rendering) или состояние страницы (view state). Эти классы имеют между собой много общего, и VB-программистам, безусловно, привычно иметь дело со многими свойствами Web-элементов управления, например Text. Но это сходство не должно вводить вас в заблуждение, поскольку и реализация, и назначение элементов управления в Web и Windows очень сильно разнятся.


202

Глава 6

Хотя в пространства имен WebControls и HtmlControls включено большинство элементов управления, которые станут базовыми для ваших элементов, это не единственные пространства имен, которые можно взять за основу для построения элементов управления. Как видно на рис. 6-1, в этой иерархии два наиболее важных класса —Page и UserControI. Page является базовым классом для всех Web-страниц в ASP.NET. Помните, что хотя класс Page используется иначе, чем остальные классы элементов управления, в своей основе он похож на любой другой элемент управления. (Очень важно изучить элементы управления. Это понадобится даже тем программистам на Visual Basic, кто не создает элементы управления, а только использует их в повседневной работе.) Так же как и Page, в качестве базового класса иногда используют похожий на него класс UserControI. Правда последний описывает не всю страницу, а ее часть. На одной странице разрешается размещать несколько объектов класса UserControI, а также вкладывать их друг в друга.

Примечание В ранних бета-версиях ASP.NET пользовательские элементы управления назывались pagelets. Это название мне нравилось больше, чем UserControI, тем не менее UserControI - это окончательное их название.

Жизненный цикл элемента управления Для осмысления работы всех элементов управления необходимо очень ясно понимать жизненный цикл элемента управления. В какой момент времени, например, восстанавливается состояние страницы (view state)? Что происходит раньше — событие Load или оповещение о событии отправки формы (postback)? В таблице 6-1 показан жизненный цикл элемента управления ASP.NET (это немного модифицированная таблица из MSDN}. Изучая таблицу, не забывайте, что HTTP — это протокол, не поддерживающий состояния. Введение понятия жизненного цикла должно создать видимость поддержки состояния формы и ее элементов. Изменяя жизненный цикл через события, можно создать иллюзию состояния (во благо или во вред — это кому как нравится).


Создание компонентов ASP.NET Таблица 6-1.

203

Жизненный цикл элемента управления ASP.NET

Фаза

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

Переопределяемый метод или событие

Инициализация

Инициализация параметров, необходимых на протяжении жизни входящего Webзапроса

Событие Init (метод Onlnit)

Загрузка состоя- Определение способа восста- Метод LoadViewState ния страницы новления состояния страни(view state) цы путем переопределения метода LoadViewState Обработка дан- Обработка входящих данных Метод LoadPostData ных, отправлен- формы и обновление свойств. ных на сервер (На этом этапе участвуют только элементы управления, обрабатывающие отправленные на сервер данные. Чтобы обработать это событие, элемент управления должен реализовывать интерфейс IPostBackDataHandler.) Загрузка

Выполняются общие для всех Событие Load (метод Onload} запросов задачи, например открытие подключения к базе данных. При событии Load создаются и инициализируются серверные элементы управ ления, восстанавливается состояние, а в элементах управления формы отражаются изменения на стороне клиента

Отправка уведомлений об изменении формы для сервера

Метод RaisePostИнициируются события об изменениях в ответ на изме- DataChangedEvent нение состояния между текущей и предыдущей отправками формы. Как и этап «Обработка данных, отправленных на сервер», этот этап имеет место только для элементов управления, реализующих интерфейс IPostBackDataHandler (см. след, стр.)


Глава 6

204 Таблица 6-1. (продолжение)

Фаза

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

Обработка событий отправки формы

Обработка события на сторо- Метод RaisePost не клиента, которое стало BackEvent причиной отправки формы и инициирует соответствующие события на сервере. Как этап «Обработка данных, отправленных на сервер», этот этап имеет место только для элементов управления, реализующих интерфейс IPostBackDataHandler

Подготовка к рендерингу

Выполнение всех изменений, Событие PreRender которые необходимы для (метод OnPreRender) рендеринга элемента управления. Рендеринг (rendering) элемента управления заключается в модификации HTML-страницы, содержащей элемент управления и отображаемой в браузере клиента. Изменения состояния, внесенные на этом этапе, сохраняются в отличие от изменений, сделанных на этапе рендеринга

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

Сохранение текущего соеМетод SaveViewState тояния элемента управления. По завершении этого этапа свойство ViewState элемента управления хранится в строковом объекте, которые отправляется на клиент как скрытое поле HTML-страницы в браузере клиента. Путем переопределения метода SaveViewState элемент управления иногда применяется для изменения свойства ViewState, например для создания более эффективного состояния страницы

Переопределяемый метод или событие


Создание компонентов ASP.NET

205

Таблица 6-1. (продолжение) Фаза

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

Рендеринг

Создание выходных данМетод Render ных, которые увидит клиент Выполняет финальную чист- Метод Dispose ку. Хотя «уборщик мусора» со временем самостоятельно освободит память от всех объектов без ссылок, здесь можно выполнить обязательное освобождение дорогостоящих ресурсов, например подключений к базе данных Выполняется завершающая Событие Unload очистка перед уничтожением (метод Onilnload] элемента управления. Как правило, разработчики элементов управления предусматривают очистку в методе Dispose и не обрабатывают это событие

Ликвидация

Выгрузка

Переопределяемый метод или событие

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

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


206

Глава 6

мент управления. Второй способ заключается з программном создании элемента управления, который затем тестируется на другой странице. Я предпочитаю создавать пользовательский элемент управления, как обычную Web-страницу, которую затем превращаю в пользовательский элемент управления. В любом случае, в результирующем файле с расширением .ascx, содержится либо код, либо атрибут Src со ссылкой на CodeBehind-файл с классом, производным от UserControl. В ASCX-файле вместо директивы @ Page размещается директива @ Control. Подготовка Web-страницы для преобразования в пользовательский элемент управления Наиболее простой способ проверки пользовательского элемента управления заключается в преобразовании Web-страницы в пользовательский элемент управления, особенно это верно для ситуаций, когда от него требуется выполнения некоторых нетривиальных задач. Например, многократного входа в систему на нескольких страницах. Для решения этой задачи я возьму простую страницу входа в систему из главы 5 и немного ее изменю (листинг 6-1). <К@ Import Namespace="System. Web. Security " J>> <html> <script language="C#" runat=server> void Login_Click(Objeot sender, EventArgs E) !

// // // // if

{

Аутентификация пользователя: эта страница предоставляет доступ только пользователю с именем doug@programmingasp.net и паролем 'password', ((UserEmail.Value == "doug@programmingasp.net") && (UserPass.Value =- "password"))

// FormsAuthentication.RedirectFromLoginPage( // UserEmail.Value, false); FormsAuthentication.GetRedirectUrl(UserEmail.Value, false);

' else Msg.Text = "Invalid Credentials: Please try again";

!


Создание компонентов ASP.NET

</script> <body> <table width=120 bgColor="OOOOff"> <tr> <td>

<form runat=server> <center> <h3> <font face="Verdana" color=Yellow>Login<font> </h3> <table width=100K> <tr> <td> <font color=yellow>Email:</font> </td> </tr> <tr> <td> <input id="UserEmail" type="text" runat=server size=20 maxlen=30 /> </td> <td> <ASP:RequiredFieldValidator ControlToValidate="UserEmail" Display="Static" ErrorMessage="*" runat=server /> </td> </tr> <tr> <td> <font color=yellow>Password:</font> </td> </tr> <tr> <td> <input id="UserPass" type=password runat=server size=20 maxlen=30 /> </td> <td> <ASP:RequiredFieldValidator

207


208

Глава 6

CentrolToValidate="UserPass" Display="Static" ErrorMessage="*" runat=server /> </td> </tr> <tr> <td colspan=3 align="center"> <asp:button text="Login" OnClick="Login_Click" runat=server> </asp:button> <P> <asp:Label id="Msg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server /> </td> </tr> </table> </center> </forn> </td> </tr> </table> </body> </html> Листинг 6-1. Форма Login.aspx из листинга 5-1, подготовленная для преобразования в пользовательский элемент управления Этот листинг отличается от листинга 5 небольшими косметическими изменениями: я добавил тэги таблицы, чтобы ограничить ширину таблицы, и задал цвет фона в соответствии со страницей, на которую предполагается разместить пользовательский элемент управления, а также немного изменил текстовые поля формы. В атрибуте size я задал размер текстовых полей, а в атрибуте maxlen — максимальное число символов в текстовых полях. Единственное существенное изменение — в функции Login_Click: if ((UserEmail.Value == "dougtprogrammingasp.net") && (UserPass. Value == "password"» { // FormsAuthentication.RedirectFroml_oginPage(

// UserEmail.Value, false); FormsAuthentication.GetRedirectUrl(UserEmail.Value, false);


Создание компонентов ASP.NET

209

Поскольку здесь используется другой метод класса FormsAuthentication, то, чтобы предотвратить переадресацию к исходной странице, я получаю и игнорирую URL-адрес переадресации. Это необходимо, так как цель обращения к RedirectFrom Login Page заключается з возвращении пользователя со страницы входа G систему, определенной в файле Web.config, на первоначально запрошенную страницу, а если такой страницы нет, то к файлу default.aspx в том же приложении. Этот пользовательский элемент управления располагается на странице в качестве компонента, который контролирует доступ пользователя к системе, причем предполагается, что пользователь остается на той же странице. При этом элемент управления не становится полноценной страницей, на которую переадресуется пользователь при запросе страницы, требующей аутентификации. В GetRedirectUrl передаются имя пользователя и параметр типа Boolean, для которых указывается, нужно ли вместе с аутентификационным билетом создавать постоянный cookie-файл.

Примечание Вокруг cookie-файлов и возможного вторжения с их помощью в частную жизнь пользователей до сих пор ведутся непрекращающиеся споры. Как вы знаете, соо(ае-файл --это«кусочек» информации, хранящийся на клиентской машине. Существует два типа cookie-файлов: кратковременные, или сеансовые (session), —они располагаются в памяти и существуют пока на клиентской машине открыт браузер, и долговременные, или постоянные (persistent), -- эти записываются на жесткий диск клиентской машины. Хороший тон требует спрашивать у пользователя разрешения на запись постоянных cookie-файлов. В результате работы ASPX-файла {листинг 6-1) создается страница, показанная на рис. 6-2.

83ак. 422


210

Глава 6

У http:/yioc^host/Cha0tei[i6__L^iG:rtrol/login asp

Рис. 6-2. Вид ASPX-страницы, готовой для преобразования в пользовательский элемент управления

Преобразование Web-страницы в элемент управления Создаваемый нами пользовательский элемента управления должен располагаться сбоку, где обычно находится панель для перемещения по страницам. Страница на рис. 6-2 уже имеет необходимый нам вид: это сравнительно узкая {120 пикселов в ширину) компактная таблица. Чтобы преобразовать Web-страницу в пользовательский элемент управления, выполните последовательность действий, описанную далее. 1. Удалите со страницы все тэги <HTML>, <BODY> и <FORM>. 2. Если на странице есть директива @ Page, замените ее директивой @ Control. {В листинге 6-1 директивы @ Page нет.) 3. Добавьте в директиву @ Control атрибут dassName. (Если директивы @ Control нет, создайте ее.) Атрибут dassNatne в пользовательском элементе управления определяет имя класса. Указание этого имени обеспечивает строгий контроль типов элемента управления при программной вставке элемента управления на страницу или в другие серверные элементы управления.


Создание компонентов ASP. NET

21 1

4. Измените расширение файла с .aspx на .ascx, чтобы отразить новое назначение файла. Полностью текст страницы Logm.ascx показан з листинге 6-2. <Х@ Control className="login" X> <%@ Import Namespace="System. Web. Security " %> <script language="Cff" runat=server> void Login_Click(Object sender, EventArgs E) { // Аутентификация пользователя: эта страница // предоставляет доступ // только пользователю с именем doug@programmingasp.net 1 // и паролем 'password . if ((UserEmail. Value == "doug@programmingasp.net") && (UserPass. Value == "password"))

{

// FormsAuthentication . RedirectFromLoginPageC // UserEmail. Value, false); Fo rmsAuthenticat ion. Get Redirect U rl ( Use rEmail. Value, false); Msg.Text = "Logged In I";

}

else

{

Msg.Text = "Invalid Credentials: Please try again";

</script> <table width=120 bgColor="OOOOff "> <tr> <td> <center> <h3> <font face="Verdana" color=Yellow>Login<font> </h3> <table width=100S!> <tr> <td> <font color=yellow>Email:</font> </td> </tr> <tr> <td> <input ld="UserEmail" type="text" runat=server


Глава 6

212 size=20 maxlen=30 /> </td> <td> <ASP: RequiredF'ieldValidator ControlToValidate="UserEmail" Display="Static" ErrorMessage="*" runat=server /> </td> </tr> <tr> <td> <font color=yellow>Password:</font> </td> </tr> <tr> <td> <input id="UserPass" type=password runat=server size=20 maxlen=30 /> </td> <td> <ASP:RequiredF-'ieldValidator ControlToValidate="UserPass" Display="Static" ErrorHessage="*" runat=server /> </td> </tr> <tr> <td colspan=3 align="center"> <asp:button text="Login" QnClick="Login_Click" runat=server> </asp:button> <P> <asp:Label id="Msg" ForeColor="Yellow' Font-Name="Verdana" Font-Size="10" runat=server /> </td> </tr> </table>


Создание компонентов ASP,NET

213

</center> </td> </tr> </table> Листинг 6-2. Пользовательский элемент управления Login.ascx, полученный путем преобразования страницы Login.aspx Как и в случае с исходным файлом Login.aspx, код сценария располагается здесь же, а не в файле программной логики. Когда нет отдельного файла с программной логикой, возникают небольшие осложнения. Во-первых, если ваш пользовательский элемент управления предназначен для других разработчиков, вам придется предоставлять ASCX-файл, исходный текст и все остальное. Если бы код сценария находился в отдельном CodeBehind-файле, достаточно было бы предоставить только исходный код ASCXфайла (который содержит только элементы пользовательского интерфейса) вместе с откомпилированной DLL-библиотекой, созданной из файла с программной логикой. Во-вторых, существуют отличия в способе регистрации компонента на странице с пользовательским элементом управления. Чтобы протестировать пользовательский элемент управления Login.ascx, я создал страницу UseLogin.aspx, показанную в листинге 6-3. <Х@ Раде Х> <%@ Register TagPrefix="Chapter06" TagName="login" Src="Login.ascx" X> <html> <head> <title>Use Login User Control</title> </head> <body leftrnargin="0" topmargin="0"> <form runat=server> <table width=600 height=600 border=C cellpadding=0 cellspacing=0> <tr> <td width=120 bgcolor="blue" valign=top> <font face="verdana" color="yellow" size=2xb> Just before the user control is included... </b></font> <Chapter06:login rD="LoginControl"


214

Глава 6

RunAt=Server /> <font face="verdana" color="yellow" size=2xb> Just after the us«r control was included... </b></font> </td> <td valign=top> <center> <br> <bxfont face="verdana" size=4> This is the rest of the page! </fontx/b> </center> </td> </tr> </table> </form> </body> </htal> Листинг 6-3. Файл UseLogin.aspx — страница с пользовательским элементом управления Login.ascx При применении любого нестандартного элемента управления ASP.NET не забудьте указать инструкцию @ Register, например: <3(@ Register TagPrefix="Chapter06" TagName="login" Src="Login.ascx" %> В этом примере атрибут TagPrefix задает префикс, который следует использовать внутри тэга, который размещает элемент управления на странице. Атрибут TagName задает имя элемента управления. Пара этих атрибутов означает, что для создания экземпляра элемента управления, описанного в ASPX-файле, требуется использовать тэг <Chapter06:login/>.

Примечание Ошибочно думать, что ASP.NET «догадается», что <Chapter06:login> является пользовательским элементом управления, а, значит, серверным элементом управления. Если в тэге <Chapter06:login> вы забудете указать пару «атрибут значение» RunAt— Server, сервер не обработает тэг и вернет его клиенту без изменении.


Создание компонентов ASP.NET

215

Атрибуте 5гс указывает местоположение ASCX-файла относительно текущего каталога, поэтому в имени файла я опустил полный путь.

Примечание В объявлениях исходного пути в ASP.NET разрешается применять один магический символ. Знак «тильда» (~) указывает на корневой каталог приложения, подобно тому, как слэш (/) указывает на корневой каталог сайта. Поначалу может показаться, что такое сокращение довольно бесполезно, однако не торопитесь с выводами: поработав с Web-приложениями, размещенными в нескольких каталогах, вы поймете его необходимость. Другая форма директивы @ Register позволяет зарегистрировать компонент, расположенный в DLL-библиотеке, которая получена путем компиляции кода. Этот способ широко применяется к элементам управления, существующих в виде готовой программы. Формат такой директивы выглядит примерно так: <К@ Register tagprefix="tagprefix" Namespace="namespace" Assembly="assembJy" K> V атрибута tagprefix тот же смысл, что и в первом варианте директивы @ Register. Атрибут Namespace задает пространство имен элемента управления. И, наконец, в атрибуте Assembly указывается имя откомпилированной .NET DLL, содержащей пространство имен, заданное атрибутом Namespace. Имя сборки указывается без расширения. .NET Framework ищет сборку сначала в частном двоичном каталоге приложения (bin), а затем в системном кэше сборок.

Что такое сборка О сборках шла речь в главе 2, и вот только что я опять упомянул о них при описании директивы @ Register. Похоже, пришла пора более подробно рассказать о них, В СОМ проблему конфликтов различных версий DLL-библиотек пытались решить с помощью неизменяемых интер-


216

Глава 6

феОсов. Но, к сожалению, такой подход не устранил проблему полностью. Как я говорил, создание новой версии СОМкомпонента, которая не нарушала бы работу существующих приложении, — даже когда интерфейс остается неизменным, — оказалось более трудным делом, чем ожидалось. В .NET эта проблема решается с помощью сборок. Сборка (assembly) — это один или несколько файлов, которые логически связаны друг с другом. Очень часто сборки состоят из единственного файла, но иногда файлов в них несколько. Сборки могут содержать исполняемый код, картинки, файлы ресурсов и т. п. Сборка — это основная единица, используемая при развертывании, поддержке версий, обеспечении безопасности и повторном использовании. Сборка содержит манифест сборки (assembly manifest) — он похож на библиотеку типов в СОМ. В листинге 2-1 показан MSIL-код простого учебного приложения «HelloDotNet». Один из разделов кода выглядит так: Assembly

Token: 0x20000001 Name : HelloDotNet

Public Key :

Nash Algorithm : 0x00008004 Major Version: 0x00000000 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> Flags : [SideBySideCompatible]

(00000000)

Здесь описывается простая сборка, лежащая в основе приложения HelloDotNet. Так в чем же отличие этого подхода от СОМ? Во-первых, СОМ-компоненты предполагают, что на каждом компьютере есть только одна версия компонента, реализующего конкретный интерфейс. В .NET Framework поддерживается параллельное исполнение (side-by-side execution), то есть разрешается одновременное сосуществование нескольких версий сборки, а разработчику и администратору предоставляется


Создание компонентов ASP.NET

217

право решать, какая из них используется приложением. При этом окончательное решение принимает администратор системы. В примерах для этой книги я обычно использую локальный каталог сборок. В этом случае никакой информации в реестр не вносится — достаточно просто скопировать файлы сборки. Сборки разрешается также размещать в глобальном кэше сборок (global assembly cache). В этом случае сборка устанавливается с помощью специальной программы, такой, как Cacutil.exe. За более подробной информацией об использовании Cacutil обращайтесь G MSDN. Загрузив UseLogin.aspx (листинг 6-3), вы увидите страницу, показанную на рис, 6-3. Пользовательский элемент управлении ChapterQ6:login I ищи user I null-»! • MieiQw.fi Ii,l«tnct fxpto*!

alho!tycbapter06_UserControl/uselogin.aspx

This is the rest of the page!

-.1

Рис. 6-3. Страница, созданная в результате работы файла UseLogin.aspx (листинг 6-3) Элемент управления должен вписываться в общий вид страницы. Копию раздела входа в систему может разместить на нескольких страницах. Действительно, в ASP разрешается включать фай-


218

Глава 6

лы на стороне сервера, однако у пользовательских элементов управления есть преимущество — в них применяется скомпилированный код. В листинге 6-4 показан HTML-код, отправляемый клиенту при запросе страницы UserLogin.aspx. <html> <head> <title>Use Login User Control</title> </head> <body leftmargin="0" topmargin="0"> <form name="_ctlO" method="post" action="uselogin.aspx" language="javascript" onsubmit="ValidatorOnSubfflit();" id="_ctlO"> <input type="hidden" name=" VIEWSTATE" value="dDwtMzg30TgxNDYyOzs+" /> <script Language="javascript" src="/aspnet_client/system_web/1_0_3125_0/WebUIValidation.js"> </script> <table width=600 border=0 height=6QO cellpadding=0 cellspacing=0> <tr> <td width=120 bgcolor="blue" valign=top> <font face="verdana" color="yellow" size=2><b> Just before the user control is included... </bx/font> <table width=120 bgColor="OOOOff"> <tr> <td> <center> <h3> <font face="Verdana" color=Yellow>Login<font> </h3> <table width=100!E> <tr> <td> <font color=yellow>Email:</font> </td> </tr> <tr>


Создание компонентов ASP.NET

<td> <input name="LoginControl:UserEmail" id="LoginControl_UserEmail" type="text" size="20" maxlen="30" /> </td> <td> <span id="LoginControl ctlO" controltovalidate="LoginControl_UserEmail" errormessage="*" evaluationfunction= "RequiredFieldValidatorEvaluatelsValid" initialvalue="" style="color:Red;visibility:hidden;">*</span> </td> </tr> <tr> <td> <font color=yellow>Password:</font> </td> </tr> <tr> <td> <input name="LoginControl:UserPass" id="LoginControl_UserPass" type="password" size="20" maxlen="30" /> </td> <td> <span id="LoginControl ct!1" cont roltovalidate= "LoginControl_UserPass" errormessage="«" evaluationfunction= "RequiredFieldValidatorEvaluatelsValid" initialvalue="" style="color:Red;visibility:hidden;">*</span> </td> </tr> <tr> <td colspan=3 align="center"> <input type="submit" name="LoginControl:_ctl2" value="Login" onclick="if (typeof(Page_ClientValidate) ==

219


220

Глава 6 'function') Page_ClientValidate(); " language="javascript" />

<span id="LoginControl_Msg" style="color:Yellow;font-family:Verdana; font-size:10pt;"> </span> </td> </tr> </table> </center> </td> </tr> </table> <font face="verdana" color="yellow" size=2xb> Just after the user control was included,., </b></font> </td> <td valign=top> <center> <br> <bxfont face="verdana" size=4> This is the rest of the page! </fontx/b> </center> </td> </tr> </table> <script language="javascript"> var Page_Validators = new Array(document.all["LoginControl document.all["LoginControl ct!1"]);

ctlO"],

</script>

<script language="javascript"> var Page_ValidationActive = false; if (typeof(clientlnformation) l= "undefined" && clientlnformation.appName.indexOf("Explorer") l= -1} { if (typeof(Page_ValidationVer) == "undefined")


Создание компонентов ASP. NET

:

221

alertC'Unable to find script library " + "'/aspnet_client/system_web/1_0_3125_0/WebUIValidation. js' ." + " Try placing this file manually, "+ "or reinstall by running 'aspnet_regiis -c'."); else if (Page.ValidationVer != "124") alertf'This page uses an incorrect version of " + "WebUIValidation. js. The page expects version 124. " + "The script library is " + Page_ValidationVer + " . " ) ; else ValidatorOnLoadO;

function ValidatorOnSubmitO { if (Page_ValidationActive) { Validate rCommonOnSubmit О ;

</script>

</form> </body> </html>

Листинг 6-4. HTML-страница, отправляемая клиенту при запросе страницы Login.ascx (листинг 6-3) В листинге 6-4 обратите внимание на одну важную деталь; тэг <Chapter06: login > (файл UseLogin.aspx, листинг 6-3) я заменил на код из Login.ascx (листинг 6-2). Пользовательские элементы управления легко создавать, и они сравнительно просты в применении. Они поддерживают вложение (nesting) элементов управления, а также размещение нескольких элементов управления на одном пользовательском элементе управления, как это сделано в UseLogin.aspx. Пользовательские элементы управления могут существовать в отдельном пространстве имен, а язык, на котором они создаются, может отличаться от языка страницы, на которой они помещены. Раньше в ASP.NET огорчал тот факт, что, в отличие от ASP, на странице в ASP.NET на стороне сервера допускался только один язык. Пользовательские элементы управления снимают это ограничение. Есть ли у пользовательских элементов управления недостатки? Рис. 6-4 иллюстрирует основную проблему с этими элементами: в Visual Studio .NET в режиме конструктора вместо истинного


222

Глава 6

представления пользовательского элемента управления отображается лишь серый прямоугольник с именем элемента.

This is the rest of the pagel

oUfaryl

Si Login, tsot

щт*#т.

S WebOKtHnCtmtroll.c

Рис. 6-4. Пользовательский элемент управления в конструкторе Visual Studio .NET Недостаточная поддержка в режиме конструктора — не ахти какое препятствие, и многие пользовательские элементы управления активно применяются в пользовательском интерфейсе, Удачным можно считать такой вариант применения: группа разработчиков делится на две команды — одна занимается пользовательским интерфейсом, другая разрабатывает программную «начинку». В этом случае, имея готовый CodeBehind-файл, разработчики пользовательского интерфейса могут работать над ASCX-файлом, а разработчики программной логики — с известной долей независимости — над файлом с программной логикой. Еще пользовательские элементы управления оказываютсяполезными, когда информация предоставляется централизовано и должна отображаться в совершенно разных пользовательских интерфейсах.


Создание компонентов ASP.NET

223

Создание нестандартных элементов управления Многих разработчиков ASP-приложений порадует повышение производительности, полученное за счет применения пользовательских элементов управления. Однако программистов на Visual Basic, осваивающих ASP.NET, разочарует слабая поддержка этих элементов в режиме конструктора. Но важнее другое: пользовательские элементы управления относятся к объектам, распространение которых независимыми разработчиками весьма проблематично. А ведь именно они внесли существенный вклад а развитие Visual Basic, и, скорее всего, их роль останется столь же важной и при развитии Visual Basic .NET и С# в рамках ASP.NET. Ограничения, свойственные пользовательским элементам управления, отсутствуют у нестандартных элементов управлений (custom controls), которые представляют собой серверные элементы управления, производные от определенного базового класса элементов управления и скомпилированные в DLL. Visual Studio .NET прекрасно поддерживает их разработку. Однако придется смириться с тем, что создавать нестандартные элементы управления сложнее, и если требуется, чтобы отображение элементов управления зависело от определенных обстоятельств (например, при размещении информации на множестве разномастных партнерских сайтов), то изменение внешнего вида придется регулировать путем изменения значений параметров.

Пример нестандартного элемента управления Сейчас вы попробуете создать простейший нестандартный элемент управления. Он отображает строку текста — так же как надпись, но располагает ее в центре страницы и выделяет текст полужирным начертанием. Я назвал его Centered Label. Решив создать нестандартный элемент управления, вы должны решить пару задач. Первая и, пожалуй, наиболее важная, — вопрос о базовом классе. Ваш новый нестандартный элемент управления будет наследовать поведение и атрибуты этого класса. В данном примере выбор прост —зю System.Web.UI.WebControls.Label.


224

Глава 6

Примечание В документации no MSDN вы найдете полное описание всех свойств, методов и событий класса System.Web.Ul.WebControls.Label. Почти see они, кроме свойства Text, являются производными от классов WebControl и Control. Использование в качестве базового класса Label означает, что все его свойства, методы и события доступны и в нашем классе. Далее надо решить, какие свойства, методы и события следует добавить. В данном примере дополнительные свойства, методы и события нам не нужны, поскольку предполагается использовать только свойство Text, унаследованное от Label. Исходный текст CenteredLabet (на Visual Basic .NET) показан в листинге 6-5. Imports System.ComponentModel Imports System.Web.UI Public Class CenteredLabel Inherits System.Web.Ul.WebControls.Label Protected Overrides Sub Render{ ByVal output As System.Web,UI.HtmlTextWriter) output.Write("<CENTER><B>" + Me.Text + "</Bx/CENTER><br>") End Sub End Class Листинг 6-5. Нестандартный элемент управления Centerediabel Этот код, в общем-то, понятен без комментариев (однако при коммерческой разработке текст следует комментировать в обязательном порядке). После инструкций импортирования располагается объявление класса по имени Centerediabel, Visual Studio .NET автоматически добавит пространство имен, назвав его так же, как проект для Visual Basic .NET. В проектах на С# пространство имен надо объявлять явно. Явное объявление пространства имен для нестандартных элементов управления Visual Basic .NET приведет к созданию вложенных пространств имен. Главным в нашем нестандартном элементе управления CenteredLabel является переопределение метода Render.


Создание компонентов ASP.NET

225

Protected Overrides Sub Render( ByVal output As System.Web.UI.HtmlTextWriter) output. Write("<CENTER><B>" + Me.Text +• "</B></CENTEflxbr>") End Sub

В таблице 6-1 видно, что этап отображения жизненного цикла элемента управления приходится на реальное формирование информационного наполнения в формате HTML. Параметр output метода Render — это экземпляр класса System. Web.UI.HtmlTextWriter, богатого на методы генерации HTML-кода. В данном случае в HTML нужно определить только свойство Text, обрамленное литеральными строками. Кажущееся удобство применения метода Write класса HtmlTextWriter не должно вводить вас в заблуждение. V этого класса есть другие методы, которые в данном случае больше подходят для генерации HTML: • код с указанием методов становится понятнее и пригоден для повторного использования, а применение этих методов не требует глубоких знаний HTML; •

методы поддерживают автоматическое преобразование между различными версиями HTML с повышением или понижением версии;

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

Применяя утилитарные методы, мы получим отличное от приведенного в листинге 6-5 переопределение метода Render. Protected Overrides Sub Render( _ ByVal output As System.Web.UI.HtmlTextWriter) output.RenderBeginTag(HtmlTextWriterTag.Center) output.RenderBeginTag(HtmlTextWriterTag.B) output.Write{Me.Text) output. RenderEndTagO output. RenderEndTagO output.RenderBeginTag(HtmlTextWriterTag.Br) output. RenderEndTagO End Sub Код стал чуть более громоздким, однако в более сложных примерах такой код не только эффективнее, но — самое главное -


226

Глава 6

з браузерах, как з новых, так более старых, результат имеет одинаково правильный вид. V метода RenderBegin Jag есть два варианта. В первом варианте ему передается строка, представляющая тэг: output.RenderBeginTag("Center") Во втором варианте используется перечисление HtmlTextWriterTag (часть пространства имен System. Web.UI}: output.RenderBeginTag(HtmlTextWriterTag.Center) Я использую перечисление, поскольку этот вариант гарантирует надлежащий регистр букв в HTML-тегах. Вообще говоря, для большинства браузеров регистр букв не имеет значения, однако я считаю соблюдение регистра хорошим тоном. Методу RenderEndTag параметры не нужны, он запишет соответствующий концевой тэг, соблюдая вложение концевых тэгов. Многие современные браузеры благодушно прощают нарушение порядка или даже отсутствие концевых тэгов. Однако лучше следить за корректностью вложения тэгов — ведь не все браузеры такие «понятливые». Соблюдение парности вызовов RenderBegin Tag и RenderEndTag обеспечит корректное завершение тэгов.

Примечание V тэга <BR> вообще-то нет концевого парного. Я вставляю здесь парные, следуя хорошему тону, а также для упрощения подсчета вызовов RenderBegin Tag и RenderEndTag. Чтобы скомпилировать нестандартный элемент управления, нужно выполнить следующую команду (все располагается на одной строке): Vbc.exe CenteredLabel.vb /reference:System.dll /reference:System.Web.dll /target:library После компиляции полученный DLL-файл (CenteredLabel.dll) копируется в папку Web-приложения ASP.NET и добавляется ссылка на этот элемент управления. Результирующая страница, которая должна выглядеть примерно так, как показано на рис. 6-5.


Создание компонентов ASP.NET

227

3hlt|>: /lutaltmst/Oiapterm,. ttnteredUibel/Wehf

This is a Centered Label

Рис. 6-5. Элемент управления Centerediabel в тестовой форме В следующем разделе вы познакомитесь с внедрением нестандартных элементов управления в Web-приложение.

Создание простого нестандартного элемента управления в Visual Studio .NET При работе над своими нестандартными элементами управления и другими проектами .NET большинство разработчиков не станут пользоваться Notepad (Блокнот), поскольку среда разработки Visual Studio .NET предоставляет поддержку CLR, а также такие удобные средства, как IntelliSense и автозавершение операторов. Хотя при желании можно обойтись Notepad. К сожалению, у Visual Studio .NET есть один недостаток: дело в том, что эта среда самостоятельно генерирует код — очень важный с точки зрения разработчиков среды, но, возможно, бесполезный для вас. Например, если создать в Visual Studio .NET проект библиотеки Web-элемента управления (Web Control Library), то код, сгенерированный средой, будет выглядеть примерно так: Imports System.ComponentModel Imports System.Web.UI <DefaultProperty("Text"), ToolboxData("<{0}:WebCustomControl1 runat=server> </{0}:WebCustomControl1>")> Public Class WebCustomControll Inherits System.Web.UI.WebCont rols.WebCont rol Dim _text As String


228

Глава 6

<Bindable(True), Category("Appearance"), DefaultValue("")> Property [Text]() As String Get Return „text End Get Set(ByVal Value As String) _text = Value End Set End Property Protected Overrides Sub Render( ByVal output As System.Web.UI.HtmlTextWriter) output,Write([Text]) End Sub End Class Здесь помимо некоторых имен, созданных по умолчанию (например, WebCustomControll], придется исправить и некоторые другие вещи. Во-первых, Visual Studio формирует класс, который наследует классу System. Web.Ul.WebControls.WebControl. Во многих случаях так и должно быть, но иногда, как в случае с элементом управления Centerediabel, требуется другой базовый класс. Кроме того, в классе появилось свойство Text и элемент данных Jext. Во многих случаях они действительно необходимы, но практически с такой же частотой они оказываются лишними. Обратите внимание на взаимосвязь между именем свойства и именем элемента данных. Регистры букв в имени свойства выдержан в стиле Pascal, а в имени элемента данных — в стиле Visual Basic .NET (где регистр не имеет значения), кроме того, в начале имени реального элемента данных класса добавлено подчеркивание. В проекте С# Web Control Library имя свойства останется тем же, но член класса получит имя text, поскольку язык С# чувствителен к регистру букв. Присутствуют различные атрибуты классов и пространств имен. Первым атрибутом, который в данном случае не присваивается по умолчанию при создании нестандартного элемента управления с помощью Visual Studio .NET, является атрибут TagPrefix. Если вы не определите его явно, Visual Studio .NET автоматически создаст свой префикс гэга, и при перемещении на форму нестандартные элементы управления получат имена с префиксом ccl,


Создание компонентов ASP.NET

229

сс2 и т. g. Атрибут TagPrefix позволяет управлять генерацией имен, наделяя их надлежащими префиксами. В следующем примере кода на С# в качестве тэгового префикса для класса RequiredTextBox устанавливается MyControls. [ assembly:TagPrefix("MyControls","RequiredTextBox") ] На Visual Basic .NET эта конструкция выглядит аналогично, только атрибут заключается не в прямые, а в угловые скобки (о), На уровне класса существует другой важный атрибут, формируемый Visual Studio .NET, — ToolboxData. Ha Visual Basic .NET этот атрибут выглядит так; <ToolboxData("<{()}:WebCustomControll rurtat=server> </{0}:WebCustomControl1>")> При перемещении элемента управления на форму Visual Studio .NET добавит свой тэг по умолчанию, однако с помощью атрибута ToolboxData можно задать дополнительные тэги, которые устанавливаются при перетаскивании каждого элемента управления на форму. В нашем примере при перетаскивании элемента управления, описанного данным классом, будет появляться пара «атрибут — значение» runat=server. Тот, кто успел набить себе шишек, забывая добавлять в код эту парочку, в полной мере оценит важность этого атрибута. В таблице 6-2 представлены атрибуты, применяемые к свойствам и поддерживаемые средой Visual Studio .NET на этапе разработки. Таблица 6-2. Атрибуты, которые присваиваются свойствам нестандартных элементов управления при работе в среде разработки Visual Studio .NET Атрибут

Описание

Bindable

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

Category DefauItValue PersistenceMode


Глава 6

230 Таблица 6-2.

(продолжение)

Атрибут

Описание

Browsable

Устанавливает, будет ли свойство отображаться в режиме конструктора Определяет используемый преобразователь типов свойств Подключает расширенный пользовательский интерфейс для установки свойства

TypeConverter Editor

Более сложный нестандартный элемент управления Пример CenteredLabel неплох для начального знакомства с нестандартными элементами управления. Однако в нем не видно даже части тех возможностей, которые дает этот тип элементов управления. Сейчас мы разработаем элемент управления, от которого будет больше толку. Очень часто при размещении текстовых полей с поясняющими надписями на Web-формах возникает множество вопросов. Как расположить текстовые поля и надписи относительно друг друга? Должно ли текстовое поле содержать текст по умолчанию? Какой назначить стиль надписи и текстовому полю? Как и в примере CenteredLabel, первый и наиболее важный вопрос — какой класс сделать базовым. Поскольку и в С#, и в Visual Basic .NET разрешено только единичное наследование, выбор нужно остановить на одном классе. В данном примере у нас есть только надпись и текстовое поле, поэтому придется решить, что же предполагается создать — надпись с текстовым полем или текстовое поле с надписью? В данном случае ответ достаточно очевиден — нам нужно текстовое поле с надписью. Иначе говоря, наш элемент управления является текстовым полем, имеющим надпись. Этот пример изрядно упрощен, однако выбирать базовый класс все равно придется, независимо от сложности элемента управления.

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


Создание компонентов ASP.NET

231

Усложненный элемент управления назовем LabelTextBox. В листинге 6-6 показан код (файл LabelTextBox.vb), содержащий объявление и реализацию класса. Imports System.ComponentModel Imports System.Web.UI Public Enum LabelLocation As Long LabelLeft LabelAbove End Enum Public Class LabelTextBox Inherits System.Web.UI,WebControls.TextBox Dim _labelText As String Dim „labelStyle As String Dim _labelLocation As LabelLocatior Property [LabelText]() As String Get Return _labelText End Get Set(ByVal Value As String) _labelText = Value End Set End Property Property [LabelStyle]Q As String Get Return _labelStyle End Get SetfByVal Value As String) _labelStyle = Value End Set End Property Property [LabelLocation]() As LabelLocation Get Return „labelLocation End Get Set(ByVal Value As LabelLocation) _labelLocation = Value End Set


Глава 6

232 End Property Protected Overrides Sub Render(ByVal output As _ System.Web.UI.HtmlTextWriter) If _labelStyle Is DBNull.Value Then output.Write("<Span Style= ) output.Write(_labelStyle) output.Write( >")

output.Write([_labelText]) output.Write("</Span>") Else output.Write([_labelText])

End If

If _labelLocation = LabelLocation.LabelAbove Then output.RenderBeginTag(HtmlTextWriterTag.Br) output.RenderEndTagO

End If

MyBase.Render(output)

End Sub

End Class Листинг 6-6. Нестандартный элемент управления LabelTextBox на Visual Basic -NET, создающий надпись и текстовое поле Как видно из листинга 6-6, после блока инструкций Imports следует объявление перечисления, которое позволит пользователю класса или компоненту описывать расположение надписи. Согласно объявлению значением перечисления по умолчанию является iabelieft; это означает, что надпись расположена в одной строке с текстовым полем, слева от него. Далее следует объявление класса, который наследует классу System,Web.UI.WebControls.TextBox. Затем объявлены три переменные-члены, хранящие свойства класса. Затем следует объявление свойства LabelText: Property [LabelText]() As String Get Return _labelText End Get Set(ByVal Value As String) _labelText = Value End Set End Property


Создание компонентов ASP.NET

233

Все три свойства сделаны по одному шаблону, независимо от типа. Объявлено каждое из свойств. В разделах Get возвращается значение элемента данных. Свойство не обязано иметь прямой аналог в реальных данных —его можно синтезировать, или создать, на основании другой информации, хранимой в классе. Например, если в классе есть свойства StartDate (начальная дата) и EndDate (конечная дата), а зам необходимо свойство Duration (длительность периода), то не нужно объявлять дополнительный внутренний элемент данных с именем ^duration — достаточно просто при запросе значения свойства Duration вычислить длительность периода. В разделе Set устанавливается новое значение свойства. По традиции передаваемый параметр называется Value, а его тип идентичен типу самого свойства. В данном примере в разделе Set внутреннему элементу данных присваивается новое значение, однако здесь можно выполнять и более сложную работу. Суть же класса LabelTextBox сосредоточена в методе Render. Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter) If _labelStyle Is DBNull.Value Then output.Write("<Span Style= ) output.Write(_label$tyle) output.Write( >") output.Write([_labelText]) output.Write("</Span>") Else output.Write([_labelText]) End If If _labelLocation = LabelLocation.LabelAbove Then output.RenderBeginTag(HtmlTextWriterTag.Br) output. RenderEndTagO End If MyBase.Render(output) End Sub Метод Render сначала проверяет, установлено ли значение для JabelStyle. Если да, записывается тэг <SPAN> с атрибутом Style, за которым следует строка_/аЬе/7ех^, а ее завершает концевой тэг </SPAN>. Если стиль не установлен, строка записывается сразу. Далее значение JabelLocation сравнивается с одним из членом LabelLocation.Label Above перечисления LabelLocation. Если над-


234

Глава 6

пись должна располагаться над текстовым полем, надо указать тэг <BR>. И, наконец, отображается само текстовое поле путем вызова метода Render типа MyBase, который на самом деле является не классом, а ключевым словом, позволяющим обратиться к членам ближайшего базового класса. Указывал MyBase, я вызываю базовую реализацию Render. В листинге 6-7 показан тот же класс, что и в листинге 6-6, но на С#. using using using using

System; System.Web.UI; System.Web.Ul.WebControls; System.ComponentModel;

namespace LabelTextBoxCS { /// <summary> /// Краткое описание WebCustomControH. /// </summary> public enum LabelLocationCS { LabelLeft,

LabelAbove 1 public class LabelTextBox : System.Web.Ul.WebControls.TextBox ( private string labelText; private string labelStyle; private LabelLocationCS labelLocation; public string LabelText

<

1

get 1

return labelText; ) set { labelText = value; )

public string LabelStyle { get


Создание компонентов ASP. NET

{

return labelStyle; } set { labelStyle = value; } }

public LabelLocationCS LabelLocation { get { return labelLocation; } set { labelLocation = value;

/// <summary> /// Выполняем рендеринг элемента управления /// для вывода требуемых параметров, /// </summary> /// <param name="output"> The HTML writer /// to write out to </param> protected override void Render(HtmlTextWriter output) { if ( labelStyle != null ) { output. Write("<Span Style=\""); output. Write(labelStyle); output. Writa("\">"); output. Write(labelText); output. Write("</Span>"); } else { output. Write(labelText); ) if ( labelLocation == LabelLocationCS. LabelAbove ) {

output. Render BeginTag(HtmlTextWriterTag. В r); output.RenderEndTagO;

235


Глава 6

236 base.Render(output); }

I

}

Листинг 6-7.

Файл LabelTextBoxCS — версия LabelTextBox на С#

Обратите внимание на небольшие различия между описаниями классов. Прежде всего, это комментарии, выделенные тройным слэшем (///}, который принят для выделения XML-комментариев. Эту информацию, которую вставляет Visual Studio, вы можете дополнить своим описанием классов и методов классов, которые должны облегчить создание XML-файла. Синтаксис свойства в С# хоть и отличается от аналогичного синтаксиса в Visual Basic .NET, но не настолько, чтобы нельзя было понять назначение кода. В обеих версиях к методам Get и Set применяется один модификатор доступа к свойству (в листингах 6-6 и 6-7 это Public). Такое неприятное ограничение можно преодолеть, описав свойство, содержащее метод Get с одним уровнем защиты, и не связанный со свойством метод, который устанавливает значение с другим уровнем защиты. Конечно, такое решение проблемы нельзя считать идеальным. В качестве примера приведу фрагмент класса, разрешающего остальным классам считывать ReadOnlyText, и отдельный метод, разрешающий классу изменять внутренний буфер, который возвращается как свойство класса ReadOnlyText: private string _readOnlyText; public string ReadOnlyText I get { return _readOnlyText; } I private setReadflnlyText(string Value) ( readOnlyText=Value; I В реальном коде свойство простого возвращения и установки базового поля следует заменить на синтез значения с применением более сложного алгоритма.


Создание компонентов ASP.NET

237

Примечание Разница между свойствами Visual Basic .NET и С# заключается в том, что в Visual Basic .NET тип свойства встречается дважды в объявлении свойства: один раз, когда свойство объявляется, и второй раз как тип параметров Value, передаваемых методу Set. В С# тип упоминается только однажды, и это более удобно, поскольку в ходе разработки тип параметра может измениться. Метод в листинге 6-7 аналогичен версии этого метода на Visual Basic .NET (листинг 6-6), если, конечно, не обращать внимание на очевидную разницу синтаксиса языков. Важнее то, что для доступа к базовой реализации класса в этих языках используются разные ключевые слова. В С# это ключевое слово base, а в Visual Basic .NET — MyBase.

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

Внимание! При работе с такими инструментами, как Visual Studio .NET (к простейшим средствам, вроде Notepad, это, конечно, не относится), иногда сталкиваешься с «самодеятельностью» инструмента, который вносит в создаваемый код свои собственные ограничения и ошибки. Так, например, в ходе подготовки этого примера я опрометчиво присвоил одно и то же имя пространству имен и классу. При


238

Глава 6

начальной проверке страницы, созданной в Notepad, она продемонстрировала ожидаемый эффект. Однако мне требовалось показать еще и поддержку нестандартных элементов управления в среде Visual Studio .NET. Я выполнил все инструкции и уже ожидал появление моего элемента управления на странице, но не тут-то было — сколько я не старался, все время появлялась какая-то странная ошибка: «CS0103: The name ' Ctrl' does not exist in the class or namespace 'ASP.WebForm1_aspx'» («Имя ' Ctrl' не существует в классе или пространстве имен ; ASP.WebForm1_aspx'»). Что за чушь, у меня нет никакого элемента с именем, начинающимся на ctr!\ Потом только я понял, что ошибка заключалась в сгенерированном Visual Studio .NET коде, который должен создавать элемент управления. Переименовав пространство имен, я решил проблему, но сколько драгоценного времени потерял впустую! Замечательно то, что ASP.NET позволяет автоматизировать многие рутинные операции, например проверку корректности данных, вводимых в текстовое поле. Однако, несмотря на автоматизацию, теперь у разработчиков забот больше, чем в Visual Basic. Вместо задания для текстового поля булевого свойства Required придется поместить на форму, связав друг с другом, два элемента управления — текстовое поле и элемент проверки входных данных (validator). Или же создать составной нестандартный элемент, сочетающий в себе и текстовое поле, и проверочный элемент. В листинге 6-8 показан исходный код на С# составного элемента управления с именем RequiredTextBox, в который вводят какой-нибудь текст. Элемент управления состоит из текстового поля и проверочного элемента управления. using using using using using using using using

System; System.10; System.Web. UI; System.Web.Ul.WebControls; System.ComponentModel; System.Collections; System.Collections.Specialized; System.Web.UI.Design;


Создание компонентов ASP.NET

239

[ assefnbly:TagPrefix("MyControls", "RequiredTextBox") ] namespace MyControls ! /// <summary> /// Краткое описание WebCustomControll. /// </summary> [ DefaultP rope rty( "Text"), ToolboxData( "<{0} : RequiredTextBox runat=server></ {0}:RequiredTextBox>"), Designer( "MyControls. RequiredTextBoxDesigner, RequiredTextBox") ] public class RequiredTextBox ; System. Web. UI. Control, INamingContainer [Bindable(true), Category ("Appearance"), DefaultValue("Text")] public string Text { get {return (string)ViewState["text"]; }

set {ViewState["text"] = value; } . [Bindable(false), Category ("Validator")] public string ErrorMessage -' get {return (string)ViewState["errorMessage"]; } set {ViewState["errorHessage"]= value; } I [Bindable(false), Category ( "Validator"), DefaultValueC"")] public string ValidatorText •i get {return (string)ViewState["validatorText"]; } set {ViewState["validatorText"]= value; } •

[Bindable(false), Category ("Validator")] public System. Drawing. Color ValidatorColor


240

Глава 6

get {

// При первом вызове инициируется исключение. // Решить проблему можно, выполняя инициализацию // из конструктора. try { return (System. Drawing. Color) ViewS tate["validatorColor"]; } catch (Exception e) ! return System. Drawing. Color. Red; }

I set {ViewState["validatorColor"]= value; }

}

protected override void CreateChildControlsO {

System. Web. Ul.WebControls. Text Box text Box; System. Web. UI. We bControls. RequiredFieldValidator requiredValidator; textBox=new TextBoxf); textBox.IO=UniqueID; text Box. Text =t his . Text; requiredValidator=new RequiredFieldValidatorQ; requiredValidator. ErrorMessage=this. ErrorMessage; requiredValidator. ForeColor=this. Validate rColo г ; requi redValidat or. Text =t his. Validate rText; requiredValidator . ID=UniqueID + "Validator"; requiredValidator. Con trolToValidate=text Box. ID; Controls. Add( text Box); Controls. Add(new LiteralControl(" ")); Controls. Add ( requiredValidator);

I public class RequiredTextBoxDesigner : ControlDesigner { public Require<JTextBoxDesigner() I I

public override string GetDesignTimeHtmlO


Создание компонентов ASP.NET I

241

RequiredTextBox rtb = (RequiredTextBox) Component; StringWriter sw = new StringWriterO; HtmlTextWriter tw = new HtmlTextWriter(sw); Hyperlink placeholderLink = new HyperLinkO; placeholderUnk.Text="RequirecJTextBox Designer"; placeholderLink.RenderControl(tw); return sw.ToString();

} i )

Листинг 6-8. Файл RequiredTextBoxCs.es — пример составного нестандартного элемента управления Прежде всего обратите внимание на включение нескольких пространств имен, которые я прежде не использовал — System.IO и System.Web.UI.Design нужны для поддержки компонента в режиме конструктора. System.Web.UI.Design требует, чтобы в проект была добавлена ссылка на System.Design.dll, которая, как и другие системные DLL, находится в каталоге < каталог_Windows>/ Microsoft. NET/Framework/< номер _версии>, Атрибуты для класса RequiredTextBox Visual Studio .NET устанавливает no умолчанию, в том числе и атрибут Designer, о котором я расскажу далее, в разделе «Расширение режима конструктора». Класс реализует особый интерфейс INamingContainer, не имеющий методов, подлежащих реализации. INamingContainer сообщает каркасу, что для обеспечения уникальности имен в приложении необходимо создать новое пространство имен. Некоторые свойства объявлены, и все они хранятся не в членах класса, а з ViewState. Обратите внимание, что в методах get и set перед возвращением значение следует привести к ожидаемому типу: public string Text

i

get {return (string)ViewState["text"]; } set {ViewState["text"] = value; }

} 9 Зак 422


242

Глава 6

Некоторые свойства относятся к элементу управления validator, поэтому с помощью атрибута Category я создаю новую категорию с именем Validator, которая позволит сгруппировать все свойства, относящиеся к этому элементу, при отображении свойств по категориям. Основную работу в классе выполняет метод CreateChildControls, Его вызывает .NET Framework при подготовке к отправке или рендерингу формы. Этот метод реально создает экземпляры текстового поля и элемента управления validator. При создании составного элемента управления может пригодиться метод EnsureChildControls, который мы здесь не использовали. Этот метод проверяет наличие дочерних элементов управления, и если их еще нет, создает их. В CreateChildControls сначала создается элемент управления TextBox, которому присваивается уникальный идентификатор, а затем его свойство Text устанавливается равным значению свойства Text составного элемента управления. Идентификатор получается с помощью свойства UniquelD, предоставляющего мне подходящий идентификатор. Этот идентификатор нужен для свойства ControlToValidate элемента управления RequiredFieldValidator, который я создам попозже. Когда экземпляр RequiredFieldValidator создан, я присваиваю ему соответствующие свойства основного элемента управления. Определяя значение идентификатора RequiredFieldValidator, я использую свойство UniquelD, добавив к нему литерал «Validator». Программисты, пишущие на C/C+ + , заметят, что здесь нет вызовов функций конкатенации, таких, как-strcat, —для объединения строк применяется оператор «+». Создав основные элемента управления, я вставляю их в составной элемент управления с помощью метода Add набора Controls. Кроме того, чтобы создать зазор между текстовым полем и элементом управления validator, я помещаю литеральный элемент управления. LiteralControl — это класс, представляющий HTMLэлементы и текст, которые не нужно обрабатывать на сервере. После установки элементов управления я могу сослаться на них из массива Controls, нумерация которого начинается с единицы. В данном примере текстовое поле — это элемент Controls[1], литеральный элемент управления — Controls^], а элемент управле-


Создание компонентов ASP.NET

243

ния validator — Control$[3]. Чтобы воспользоваться массивом Controls, в общем случае нужно привести элемент массива к корректному типу. Например, для обращения к элементу управления, представляющему текстовое поле, следует применить выражение ((TextBox)Controls[1J).

Композиция или рендеринг? Составной элемент управления можно создавать в виде композиции (composition] или формируя его визуальное представление, то есть посредством рендеринга (rendering). Первый вариант позволяет создавать самостоятельные объекты, манипулировать их свойствами и вставлять их в окончательный элемент управления. Выбрав второй, вы ограничитесь созданием произвольного HTML-кода, который отформатирован нужным образом. В данном примере мы выбрали композицию, когда создаются несколько серверных элементов управления и их отображением управляет каркас ASP.NET. Эти действия выполняет метод CreateChildControls. В другом варианте применяется метод Render. Напомню, что к этому методу я обращался, когда рассказывал об элементе управления Centered Label. Событие Render применимо и для составных элементов управления, однако при большом количестве элементов управления этот способ становится неудобным, а у вас появится дополнительная работа. Если вместо создания серверных элементов управления, при котором можно управлять свойствами элементов, используется рендеринг, необходимо создавать дополнительный код для отображения элементов управления HTML. Аналогичная ситуация описывалась выше для несоставного элемента управления Centered Label, но данный случай намного сложнее, поскольку в коде помимо прочего требуется определить взаимоотношения между двумя элементами. Кроме того, необходимо реализовать два дополнительных интерфейса — IPostBackEventHandler и IPostBackDataHandler. И, наконец, нужно переопределить метод Render. Пример RequiredTextBox плохо подходит для реализации с применением рендеринга, поскольку полученный в резуль-


244

Глава 6

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

Внедрение элемента управления в пользовательский интерфейс Visual Studio .NET Внедрение элемента управления в пользовательский интерфейс проекта Visual Studio .NET выполняется одним из двух способов. Во-первых, можно вручную скопировать элемент управленил в папку с двоичным кодом или вручную добавить в форму тэг @ Register. Это сделать нетрудно, но если вы доверяете Visual Studio .NET, есть и более простой путь. Вначале откройте проект, в котором предполагается использовать элемент управления. Откройте в режиме конструктора одну из Web-форм проекта и выберите вкладку Toolbox, куда вы собираетесь добавить элемент управления. Щелкнув правой кнопкой мыши Toolbox, выберите в контекстном меню команду Customize Toolbox. В появившемся диалоговом окне откройте вкладку .NET Framework Components и щелкните кнопку Browse. Найдите и раскройте папку, в которой расположена сборка с нужным вам элементом управления. Обычно это папка с двоичными файлами проекта (bin) или папка bin\Release для проекта на С#. Выберите сборку — файл с расширением .dli. После этого элемент управленил должен появиться в списке элементов управления на вкладке .NET Framework Components. Отметьте нужный элемент управления (рис. 6-6) и щелкните в диалоговом окне Customize Toolbox кнопку ОК. Когда элемент управления добавляется на Web-форму первый раз, происходит следующее: •

к директиве @ Register на странице добавляются атрибуты TagPrefix и Namespace;

• на страницу добавляется тэг элемента управления; • в проект добавляется ссылка на элемент управления. Результат этого такой же, как от копирования сборки в папку с двоичными файлами приложения;


Создание компонентов ASP.NET

245

• при необходимости загружается режим конструктора элемента управления; •

элемент управления отображается в режиме конструктора, .-•::

': •'• ОМ Components

5

=:,•,;; i

••'

,f\

II

.NETFrai

\ Assembly Кипе

jf*l \\

Name

Nm-iespate

П Re mot ing Service

System , Runtime . Rerouting ....

System.Runtime.Remct'ng (1.0 24. .

0 Repeater

System , Web , UI , WebControls

П ReportClass

IrystalDedsions.CnstalReF..

5y5tem.Web(1.0.2411.0) CrystalDecisions.CrystalReporti.Bngin...

0 RepcrtDocument

i

Crystal Decisions .CrystalHepcrts.Engin...

'

Cry st a Decisions . CrystdlRep. ,

CrystalOeasions.CrystalReports.Engh...

i

П ReportServiceBase

Cry sralDecis ions, Web, 5ervi..

CrystalDecisions.Web(9,1.0.0)

0 PequredFiekJVdlidator

System , Web , UI. WebControls

System.Web(t. 0.241 1.0)

ElRthTextBox

System, Windows. Forms

System. Windows, Fr^rms (1.0.2411.0)

ElsavefileDialog

System. Windows. Form?

System. Windows. Forms (1. 0,2411.0)

1^1^ ,

О ReportDocument

ЙСЙК

Version-

i • 7.0, С

> «

Brpwee...

,

I

«rj iReirdJ-

1

21-

Caneet

Beiet

Hflp

Рис. 6-6. Добавление элемента управления RequiredTextBox на панель Toolbox Конечно, ни одну из этих задач нельзя назвать трудной, однако чем больше процедур среда разработки выполняет автоматически, тем меньше головной боли у программиста. В остальной части книги компоненты, используемые в примерах, я буду добавлять на панель Toolbox. На рис. 6-7 показана простая форма с помещенным на нее элементом управления RequiredTextBox.


246

Глава 6

S«ia Enla &еЧш|

••

Рис. 6-7. Элемент управления RequiredTextBox в режиме конструктора Visual Studio .NET В листинге 6-9 показан код страницы WebFormI .aspx, на которой размещен RequiredTextBox. <%@ Register TagPrefix="requlredtextbox" Namespace="MyControl£" Assembly="RequiredTextBox" %> <3S@ Page language="cff" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="Chapter06_TestRequiredTextBox.WebFormI" X> ODOCTYPE HTML PUBLIC "-//WSCX/DTD HTML 4.0 Transitional//EN" > <HTML> <HEAD>

<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" Content="Cfl"> <meta name="vs_defaultClientScript" content="JavaScript (ECMAScript)"> <meta name="vs_targetSchema" content="http://S3hemas.microsoft.com/intellisense/ie5"> </HEAD>


Создание компонентов ASP.NET

247

<body> <forffl id="Form1" fflethod="post" runat="server"> <TABLE cellSpacing="1" cellPadding="1" width="600" border="0"> <TR height="100"> <TD width="50X" align="right"> <asp:Label id="Labe!1" runat="server"> Must Enter: </asp:Label> </TD> <TD> <RequiredTextBox:RequiredTextBox id="Requi redTextBoxl" runat="server" ErrorMessage="*"> </RequiredTextBox:RequiredTextBox> </TO> </TH> <TR> <TD colspan="2" align="middle"> <asp:Button Runat="server" Text="Submit" id="Button1"> </asp:Button> </TD> </TR> </TABLE> </form> </body> </HTML> Листинг 6-9. Файл Web For ml.asp x — страница, используемая для тестирования элемента управления RequiredTextBox В листинге 6-10 представлен код (файл WebForml.aspx.cs) программной логики страницы. using using using using using

System; System.Collections; System.ComponentModel; System.Data; System.Drawing;


248

Глава 6

using using using using using

System.Web; System.Web.SessionState; System.Web.UI; System.Web.Ul.WebControls; System.Web.UI,HtmlControls;

namespace Chapter06_TestRequiredTextBox /// <summary> /// Краткое описание WebForml. /// </summary> public class WebForml : System.Web.UI.Page protected MyControls.RequiredTextBox RequiredTextBoxl; protected System.Web.Ul.WebControls.Button Buttonl; protected System.Web.Ul.WebControls.Label Labell; public WebForm1() Page.Init += new System.EventHandler(Page_Init); )

private void Page_Load(object sender, System.EventArgs e) // Разместите здесь пользовательский код // инициализации страницы. I

private void Page Init(ob]ect sender, EventArgs e) i // CODEGEN: This call is required by // the ASP.NET Web Form Designer. InitializeComponentO; ffregion Web Form Designer generated code /// <summary> /// Этот метод необходим для поддержки Designer /// Не изменяйте его средствами редактора кода. /// </summary> private void InitializeComponentO 1 this.Load += new System.EventHandler(this.Page.Load);


Создание компонентов ASP.NET

;

249

ffendregion

Листинг 6-10. Файл с программной частью WebForm1.aspx.cs, написанный на С# для страницы тестирования составного элемента управления RequiredTextBox На рис. 6-8 показана страница проверки элемента управления RequiredTextBox go ввода текста. . ;;!

* E* $ew Favorites lools- .НФ :

s

!

:;i|; -;'

:

-;;y> -.'-»4. - g -^'^ ! t£Peiwr>alB« ^Search 14|FavovS« '^|, Й*!]|!] http:i'/lcicariosychepter06_Tesi:RequiredTeitBcix№ebFa-ml.aspx

-j

Tj

Must Enter

Рис. 6-8. Тестовая страница нестандартного элемента управления RequiredTextBox На рис. 6-9 показана страница после попытки отправить ее на сервер и активизированный элемент проверки ввода в виде «звездочки» (*). Проверка наличия заполнения поля (рис. 6-9) выполняется на клиенте. Добавьте пару «атрибут —значение» ClientTarget=Down level в директиву @ Page, и при каждом, щелчке кнопки Submit введенные данные будут обрабатываться на сервере.


250

Глава 6

Must Enter1

Рис. 6-9. Тестовая страница для нестандартного элемента управления RequiredTextBox после отправки страницы с незаполненным полем

Примечание Есть другие способы отключения проверки наличия данных (без блокировки рендеринга всей страницы). Во-первых, для события Click кнопки Submit можно задать Page_ValidationActive = false. Такой способ годится, если на форме есть кнопка отмены и нужно, чтобы при ее нажатии на стороне клиента не выполнялась проверка наличия текста в поле. Во-вторых, - - и это, пожалуй, лучший способ — можно установить значение атрибута CausesValidation в тэге <asp:Button> в false. В этом случае отключается проверка и на стороне клиента, и на стороне сервера, что как нельзя лучше подходит для кнопки отмены. Расширение режима конструктора Вернемся к рис. 6-7. Обратите внимание на выбранный элемент управления, где отображается следующий текст «RequiredTextBox Designer». По умолчанию в режиме конструктора элемент управления покажет только то, что должно отображаться при обращении к RenderControl. Если при вызове этого метода ничего не отображается, Visual Studio .NET покажет тип и идентификатор


Создание компонентов ASP.NET

251

элемента управления, например «RequiredTextBox:RequiredTextBoxl». Если вы хотите, чтобы в режиме конструктора элемент управления отображался в другом виде, у вас есть выбор. Лля отображения текста обычно используют атрибут Designer класса. В листинге 6-8 он выглядит так: Designer("MyControls.RequiredTextBoxDesigner, RequiredTextBox") Атрибут Designer сообщает среде разработки, что MyControls. RequiredTextBoxDesigner — это класс, выступающий в роли дизайнера (designer), и он входит в сборку RequiredTextBox. Я видел исходные тексты с явным указанием расширения сборки (.dll), однако в этом нет необходимости, и для согласованности с другими разделами ASP.NET, например с директивой @ Register, оно здесь опущено. Класс-дизайнер часто располагается в дополнительном пространстве имен, например в MyControls.Design, хотя может находиться в любом пространстве имен и в любой сборке, Помещение класса-дизайнера в ту же сборку, что и компонент, более удобно, хотя и увеличивает размер сборки, даже если элемент управления никогда не открывается в режиме конструктора. Однако присутствие класса-дизайнера в той же сборке не снизит производительность исполняемого кода. Класс RequiredTextBoxDesigner, расположенный в конце листинга 6-8, наследует классу ControlDesigner. Класс-дизайнер должен наследовать одному из следующих классов: • System.Web.UI.Design.ControlDesigner — дизайнер общего назначения, производный от Control и WebControl; • System. Web. Ul. Design. WebControls.TemplatedControlDesigner —-добавляет поддержку редактирования шаблонов. Более подробно об элементах управления на базе шаблонов я расскажу в главе 9; • System. Web. Ul. Design. WebControIs.ReadWriteControlDesigner — добавляет поддержку редактирования по месту, как в элементе управления Panel. Благодаря такой поддержке можно в режиме конструктора размещать одни элементы управления на другие. Благодаря наличию конструктора без параметров класс RequiredTextBoxDesigner не нуждается в каких-либо параметрах.


252

Глава 6

Основную работу класс-дизайнер выполняет в методе GetDesignTimeHtml. Этот метод сначала получает экземпляр элемента управления, который способен получать или задавать параметры. В данном случае он нужен только для демонстрации того, как получить текущий экземпляр класса. Создаются объекты классов StringWriter и HtmlTextWriter (в пространстве имен System.IO). Создается новый объект Hyperlink и отображается поле ссылки. Текст объекта гиперссылки устанавливается в «RequiredTextBox Designer», хотя здесь может быть любой другой HTML-код. Есть еще способ изменить отображение элемента управления в режиме конструктора. В этом случае появляется серый выпуклый прямоугольник наподобие кнопки. Суть этого способа состоит в использовании метода CreatePlaceHolderDestgnTimeHtml, которому в качестве параметре передается строка, отображаемая в этом сером прямоугольнике. Снова взгляните на рис. 6-7 и обратите внимание на две особенности в окне Properties. Во-первых, элементы в этом окне отображаются по категориям, поэтому параметры, в объявлении которых упомянута категория Validator, размещены рядом. Кроме того, свойство ValidatorCotor это не просто текстовое поле для ввода имени или номера цвета, а полноценное окошко выбора цвета (рис. 6-10).

Рис. 6-10. Окно выбора цвета, доступное для нестандартного элемента управления RequiredTextBox


Создание компонентов ASP.NET

253

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

Заключение Большинство программистов, пишущих на Visual Basic, платформа ASP.NET привлечет главным образом наличием в ней пользовательских и нестандартных элементов управления. Такие программисты давно привыкли к наличию широкого набора компонентов, что позволяет заниматься непосредственно решением конкретных бизнес-задач. Наряду с пользовательскими элементами управления, создаваемыми, как правило, внутри компании, и библиотеками нестандартных элементов управления, распространяемыми сторонними поставщиками, компоненты появятся на подавляющем большинстве страниц ASP.NET. Надеюсь, что этот краткий экскурс в создание элементов управления поможет оценить их огромные возможности. Хотя в Visual Basic были очень популярны элементы управления VBX, однако ActiveX-элементы не отстают, особенно после того, как реализована возможность создавать их в Visual Basic. Думаю, вряд ли кто станет спорить, что уже в версии 1.0 ASP.NET является удобным и простым инструментом для создания элементов управления. В следующих главах я расскажу о других особенностях пользовательских и нестандартных элементов управления. В главе 7 вы узнаете, когда обработку уместнее выполнять на клиенте, а когда — на сервере, а также в каких случаях применяют комбинированную обработку одновременно и на сервере, и на клиенте, Главы 8 и 9 посвящены организации доступа к базам данных и XML. Существует множество встроенных элементов управления, работающих с данными, но можно создать еще больше нестандартных элементов управления для эффективного доступа к информации.


Глава 7

Зайдите в любое время з список рассылки, посвященный ASP.NET, и вы наверняка найдете там, например, такое крайне возмущенное послание программиста, весьма поверхностно знакомого с разработкой Web-приложений вообще и с технологией ASP.NET в частности: «Если С# (или Visual Basic .NET) такой мощный язык, то почему же мне не удается заставить приложение ASP.NET вывести простейшее информационное окно?!» Опытные разработчики «классических» ASP-страниц, возможно, улыбнутся такому вопросу, но для многих программистов, не имеющих опыта Web-разработки, это действительно проблема. Почему же нельзя вывести обычное информационное окно, как в Visual Basic? В сказке «Волшебник изумрудного города» Элли в один прекрасный момент обнаружила, что она больше не в Канзасе, — это и есть ответ на наш вопрос: потому что вы больше не занимаетесь программированием клиентской части. ASP.NET —это серверная технология. Она позволяет воспользоваться лучшими и самыми современными возможностям серверного программирования, но если вы хотите использовать ее для вывода сообщений, то вы обратились не по адресу. Конечно, в ASP.NET есть возможность отображать информационные окна, а также другие способы взаимодействия с пользователями, но для этого потребуется создать клиентский сценарий.


Распределение функций между сервером и клиентом

255

Примечание В JavaScript для отображения информационных окон применяется функция alert. В этой главе я расскажу об основах создания клиентских сценариев, но это не руководство no JavaScript. Об этом языке написано много хороших книг, например «JavaScript: The Definitive Guide», David Flanagan. O'Reilly, 1996).

Разработка клиентских сценариев Разрабатывая Web-приложения, необходимо помнить одну важную веш,ь — пока пользователь работает со страницей, существует очень хрупкая связь между клиентским браузером и сервером. Отсутствие постоянной связи — особенность протокола HTTP. Так как во время работы Web-приложения клиент и сервер большую часть времени не взаимодействуют, один сервер способен поддерживать одновременно сотни пользователей. На рис. 7-1 показана модель Web-приложения. Клиентский браузер

Получение страницы с сервера

Web-сервер

Клиентский браузер

Клиентский браузер

Работа со страницей и ввод данных осуществляются без обращения к серверу

Клиент отправляет на сервер страницу с заполненной формой

Web-сервер

Рис. 7-1. Иллюстрация взаимодействия клиентского Webбраузера и сервера


256

Глава 7

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

Примечание В некоторых случалх на стороне клиента применяют VBScript (Visual Basic Scripting Edition), но компоненты ASP.NET его не поддерживают. В примерах этой главы вместо VBScript я использую JScnpt — версию JavaScript корпорации Microsoft. Совет В ASP.NET языки JavaScript и jScript получили новое крещение и новое имя — в документации на ASP.NET они называются ECMAScript. Этот язык не так известен, как JavaScript или JScript, однако он поддерживает совместимость с различными браузерами. Более подробную информацию о языке сценариев ECMAScript вы найдете по адресу http://www.ecma.ch/ ecma1/stand/ecma262.htm. В браузерах в качестве значений атрибута Language тэга <SCRIPT> продолжают использовать JavaScript или JScript. Обращение к серверу инициируют лишь несколько стандартных элементов управления HTML — первыми вспоминаются кнопки ввода и гиперссылки. Ограниченность числа элементов управления, инициирующих взаимодействие с сервером, не случайно. Далее вы узнаете, что существуют другие элементы управления, инициирующие обращение к серверу. Для этого применяются клиентские сценарии на JavaScript. С их помощью, к примеру, можно вызвать обращение к серверу, когда пользователь выходит из раскрывающегося списка. Вообще говоря, этот способ не


Распределение функций между сервером и клиентом

257

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

Внимание! Поскольку вы не в состоянии управлять клиентскими сценариями, их рекомендуется использовать только для сокращения числа обращений к серверу. Они не заменят проверку данных на стороне сервера. Программа на стороне сервера должна считать все данные, поступающие из форм, в том числе уже проверенные клиентским сценарием, ненадежными до тех пор, пока не доказано обратное. Кроме того, при использовании менее современного браузера клиентские сценарии в стандартных элементах управления [например, в элементах проверки правильности вводимых данных (validator)] не работают. Клиентские сценарии в ASP.NET Раскрывающийся список —это стандартный элемент управления, который часто применяется в формах. Параметры формы изменяются в зависимости от выбранного пользователем элемента списка. Выбор элемента совсем не обязательно инициирует обращение к серверу — оно инициируется клиентским сценарием. В листинге 7-1 показан пример кода (файл PostTest.aspx), созданного в Visual Studio .NET.


258

Глава 7

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="PostTest.aspx.vb" Inherits="ChapterQ7_DropDownPost.PostTest"S> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD>

<t it 1 ex/title > <meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0"> <meta name="CODE_LANGUAGE" content="Visual Basic 7.0"> <meta name="vs_defaultClientScript" content="JavaScript"> <nteta name="vs_targetSchema" content="http://sc:hemas. microsoft, com/intellisense/ie5"> </HEAD> <tmdy> <form id="Form1" method="post" runat="server"> <table width="600" border="0"> <tr> <td align="midd3e"> <asp:dropdownlist id="DropDownList1" runat="server" AutoPostBack="True"> <asp:Listltem Value="Black"> —Select Color—</asp: Listltem> <asp:Listltem Value="Red">Red</asp:Listltem> <asp:Listltem Value="Green">Green</asp:Listltem> <asp:Listltem Value="Blue">Blue</asp:Listltem> </asp:dropdownlist> <br> <br> <asp:Label id="Labe!1" runat="server"x/asp: Label> <br> <br> <br> </td> </tr> </table> </form> </body> </HTML>

Листинг 7-1. Файл PostTest.aspx — страница, инициирующая отсылку данных на сервер при выборе нового элемента в раскрывающемся списке


Распределение функций между сервером и клиентом

259

Эта программа (слегка измененная для этой книги) создает простую форму с надписью и раскрывающимся списком. Надпись изначально пуста. Начальное значение списка — «•—Select Color—», кроме того, в нем предусмотрено три варианта выбора -— Red, Green и Blue. В дополнение к обычным атрибутам тэга asp:dropdownlist атрибут AutoPostBack установлен в True. Атрибут AutoPostBack присутствует в различных элементах управления, в том числе в раскрывающихся и обычных списках, флажках и текстовых полях ввода. Если таком элементе управления значение атрибута AutoPostBack равно True и в этот элемент вносятся изменения (выбирается элемент в списке, изменяется состояние флажка или текст в текстовом поле), выполняется обращение к серверу, который каким-то образом реагирует на это изменение. В листинге 7-2 показана программная логика (PostText.aspx.vb) страницы Post Test. aspx. Public Class WebForml Inherits System.Web.UI.Page Protected WithEvents DropDownListl As System.Web.UI.WebControls.DropDownList Protected WitriEvents Labell As _ System.Web.Ul.WebControls.Label ftRegion " Web Form Designer Generated Code " 'Этот вызов необходим для WeD Form Designer. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponentQ

End Sub

Private Sub Page_Init(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Init 'CODEGEN: Этот вызов необходим для Web Form Designer 'He редактируйте его вручную InitializeComponent{) End Sub #End Region Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs} _ Handles MyBase.Load


260

Глава 7 'Сюда можно добавить код для инициализации страницы If IsPostBack Then If DropDownListl.Selectedlndex <> 0 Then Labell.Text = "You selected " + _ DropDownListl.Selectedltern,Text Labell.ForeColor = Labell.ForeColor.FromNamef _ DropDown List LSelectedltem. Value) Else Labell.Text = "Please select a color" Labell.ForeColor = Labell.ForeColor.FromNameC'Black")

End If End If End Sub End Class Листинг 7-2. Файл PostText.aspx.vb с исходным текстом сценария PostTestaspx (см. листинг 7-1) Важнейшая особенность листинга 7-2 — использование метода Page_Load для проверки того, выполнялось ли обращение к серверу. Если да, значит, пользователь выбрал элемент з раскрывающемся списке, поэтому в надписи отображается название выбранного цвета и ее цвет соответственно изменяется. Как видно из следующего примера, определение цвета текста несколько отличается от задания самого текста. Labell.ForeColor = Labell.ForeColor.FromName( _ DropDown List LSelectedltem. Value) В отличие от предыдущих версий Visual Basic, здесь свойство ForeColor — это не простое число, определяющее интенсивность красного, зеленого и синего, а особый тип — System.Drawing.Color. Например, чтобы отобразить красный цвет, нельзя просто присвоить свойству ForeColor значение 255, — для этого используются несколько дополнительных функций, позволяющих задать цвет по его названию (как в данном примере) или системный цвет, например ActiveBorder. Вы также вправе воспользоваться одним из цветов, определенных в классе System.Drawing.Color от темно-коричневого (SaddleBrown) до цвета толченого миндаля (BlanchedAlmond). Поскольку в данном случае атрибут Value каждого из тэгов asp:Listltem представляет собой название цвета, его можно передать от выбранного элемента методу From-


Распределение функций между сервером и клиентом

261

Name u присвоить полученное значение System.Drawing.Color свойству ForeColor. На рис. 7-2 показана страница, отображаемая после выбора в раскрывающемся списке элемента Green (Зеленый). (Обратите внимание, что на странице нет кнопки отправки формы.) Favorites

Tonls

—Pe

Рис. 7-2. Страница PostTest.aspx после выбора в раскрывающемся списке элемента Green На рисунке видно, что страница распознала выбор элемента Green. (Вам придется поверить мне на слово, что надпись зеленого цвета.) Если на этой странице нет кнопки отправки данных формы и созданный средствами HTML раскрывающийся список не вызывает обращение к серверу, то как же атрибуту AutoPostBack удается инициировать это обращение? Ответ — в листинге 7-3, где показан реальный HTML-код, получаемый браузером. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD>

<tltlex/title> <meta name="GENERATOR" content= 1 1 Microsoft Visual Studio. NET 7.0"> <meta name="CODE_LANGUAGE" content="Visual Basic 7.0"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" соntent="http://schemas.microsoft,com/intellisense/ie5"> </HEAD>


262

Глава 7

<body> <form name="Form1" method="post" action="PostTest.aspx" id="Form1"> <input type="hidden" name=" VIEWSTATE" value="dDw4MzQ2Mzg4MzY7dDw7bDxpPDE+Oz47bDxOPDtsPGk8Mz47Pjts PHQ8cDxwPGw8VGV4dDtGb3JlQ29sb3I7XyFTQjs+02w8WW91IHNlbGVjdGV kIEdyZWVuOzI8R3JlZW4+02k8NT47Pj47Pjs7P3S+Pjs+Pjs+" />

<table width="600" border="0"> <tr> <td align="middle"> <select name="DropDownList1" id="DropDownList1" onchange=" doPostBack('DropDownListl','')" language="javasc:ript"> <option value="Black">—Select Color—</option> <option value="Red">Red</option> <option selected="selected" value="Green">Green</option> <option value="Blue">Blue</option> </select> <br> <br> <span id="Labe!1" style="color:GrEien; ">You selected Green </span> <br> <br> <br> </td> </tr> </table> <input type="hidden" name=" EVENTTARGET" value="" /> <input type="hidden" name="__EVENTAnGUHENT" value="" /> <script language="javascript"> <! — function doPostBack(eventTarget, eventArgument) { var theform = document.Forml; theform. „EVENTTARGET. value = eventTarget; theform. „EVENTARGUMENT.value = eventArgument; theform.submitf); // --> </script> </form>


Распределение функций между сервером и клиентом

263

</body> </HTML> Листинг 7-3. HTML-код, генерируемый страницей из листингов 7-1 и 7-2 Важной особенностью HTML-кода в листинге 7-3 является JavaScript-функция doPostBack между тэгами <SCRIPT> и </SCRIPT> и атрибут onchange элемента DmpDowniistl. В листинге 7-1 нет никакого события onchange, а в листингах 7-1 и 7-2 — никаких JavaScript-сценариез. Но они все же передаются браузеру в составе страницы. Добавление данного сценария JavaScript генератором ASP.NET позволяет инициировать обращение к серверу при выборе элементов списка. Функция doPostBack из листинга 7-3 работает с двумя скрытыми полями формы - _EVENTTARGET и __EVENTARGUMENT. Значения этих полей изначально пусты и заполняются функцией _doPo$tBack перед вызовом функции theform.submit. Во время обращения к серверу страница использует эти значения, чтобы определить, какой из элементов управления изменился и вызвал данное обращение.

Инициирование обращения к серверу из пользовательских элементов управления Некоторые элементы управления поддерживают атрибут AutoPostBack и поэтому в состоянии при изменениях обращаться к серверу. К сожалению, вновь создаваемые пользовательские элементы управления не всегда базируются на элементах, поддерживающих AutoPostBack, но, к счастью, это решаемая проблема, Класс Page предоставляет метод GetPostBackEventReference, который создает клиентский сценарий, позволяющий элементу управления инициировать обращение к серверу. Получается JavaScript-сценарий почти такой же, как в листинге 7-3. Листинг 7-4 представляет элемент управления «гиперссылка», созданный в Visual Basic .NET, которая в ответ на щелчок вызывает обращение к серверу, а не открывает другую страницу, как это обычно происходит при щелчке стандартных гиперссылок. Imports System.ComponentModel Imports System.Web.UI <Assembly: TagPrefix("Post Link", "PostLinkStuff")>


264

Глава 7

<DefaultProperty("Text"), ToolboxDataf _ "<{0}:PostUnkControl runat=server></{0}:PostLinkControl>")> _ Public Class PostLinkControl Inherits System.Web.Ul.WebControls.WebControl Dim .text As String <Bindable(True), Category("Appearance"), DefaultValue("")> _ Property [Text]() As String Get Return _text End Get Set(ByVal Value As String) _text = Value End Set End Property Protected Overrides Sub Render( ByVal output As System.Web.UI.HtmlTextWriter) output.Write("<a id-""" + Me.UniquelD + _ href=""javascript:" + Page.GetPostBackClientEvent(Me, „text) + output.Write(_text + "</a>") End Sub

>")

End Class Листинг 7-4. Файл PostLink.vb — ссылка, инициирующая обращение к серверу

Примечание В ASP.NET есть элемент управления LinkButton, выполняющий большинство функций элемента управления в данном примере. В общем, показанный метод лучше использовать для более сложных элементов управления, но демонстрация данной возможности на примере гиперссылки облегчает понимание. Показанный в листинге 7-4 файл PostLink.vb я создал как проект Visual Basic Web Control Library s Visual Studio .NET. Я добавил к нему следующую строку: <Assembly: TagPrefixf"Post Link", "PostLinkStuff")>


Распределение функций между сервером и клиентом

265

Этот тэг указывает Visual Studio .NET использовать префикс PostLinkStuff в именах всех элементов управления из пространства имен PostLink, которые размещаются на форме. В главе 6 я говорил, что, если не задать атрибут JagPrefix, Visual Studio .NET назначает имена сс1, сс2 и т. д. Вторая часть программы — метод Render — взята с некоторыми изменениями из проекта Web Control Library. Protected Overrides Sub Render( ByVal output As System.Web.UI.HtmlTextWriter) output.Write("<a id= + Me.UniquelD + " href=""javascript:" +

Page.GetPostBackClientEvent(Me, _text) + output.Write(_text + "</a>") End Sub

>")

Один из недостатков метода Render заключается в необходимости написания большого числа кавычек в вызове функции output.Write. Каждую добавляемую в строку кавычку следует повторять дважды, иначе одинарная кавычка расценивается как конец строки. На С# этот же метод выглядит так: protected override void Render(HtmlTextWriter output) { output.Write("<a id=\"" + this.UniquelD + "\" href=\"javascript:" + Page.GetPostBackClientEventfthis, text) + "\">") output.Write(text + "</a>") } В С# применяются стандарты С/С+ -f, согласно которым в символьной строке с кавычками знаку кавычки предшествует обратная наклонная черта. Кроме того, для указания текущего экземпляра класса в С# используется ключевое слово this, в Visual Basic .NET—Me. Метод Render создает тэг-анкер со ссылкой на JavaScript-код в атрибуте href. JavaScript вызывает функцию Page.GetPostBackClientEvent, которой передается ссылка на текущий экземпляр (с помощью Me в Visual Basic .NET или this в С#) и значение переменной _text. Как показано в методе Page_Load исходного текста файла программной логики (code-behind) TestPostiink.aspx.vb (ли-


266

Глава 7

стинг 7-5), оба эти значения доступны на сервере — они хранятся в переменных JVENTTARCET и JVENTARCUMENT. Public Class WebForml Inherits System.Web.UI.Page Protected WithEvents PostLinkControll _ As PostLink.PostLinkControl Protected WithEvents Labell _ As System.Web.UI.WebControls.Label ((Region " Web Form Designer Generated Code " 1

Этот вызов необходим для Web Form Designer <System,Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() End Sub

Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: Этот вызов необходим для Web Form Designer 'He редактируйте его вручную InitializeComponentO

End Sub «End Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load If Me.IsPostBack = True Then Labell.Text = "Postback from " + _ Request Cn_EVEMTTARGET") + " - " + _ Request("__EVENTARGUMENT")

End If End Sub End Class Листинг 7-5. Исходный текст файла TestPostLink.aspx.vb для тестирования элемента управления PostLink В листинге 7-6 показано, как проверяется работа элемента управления PostLink. (файл TestPostLink.aspx),


Распределение функций между сервером и клиентом

267

<'S@ Register TagPrefix="PostLink" Namespace=" Post Link" Assertibly="PostLink" %> <%@ Page Language="vb" AutoEventWireup=" false" Codebehind="WebForm'i.aspx. vb" Inherits="Chapter07_PostControl.WebForm1"X> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitlonal//EM"> <HTML> <HEAD>

<title></title> <meta name="GENERATOR" ccmtent="Microsoft Visual Studio. NET 7 . 0 " > <meta name="CODE_LANGUAGE" content="Visual Basic 7 . 0 " > <meta name="vs_def аи It Client Script" content="JavaScript"> <meta name="vs_target$chena" content="http://schemas. microsoft. com/in tellisense/ie5"> </HEAD> <body> <form id="Form1" method="post" runat="server"> <PostLink: PostLinkControl id="PostLinkControir runat="server" Text="This is a test"> </PostLink: PostLinkControl> <asp: Label id="Labe!1" runat="server"x/asp: Label> </form> </body> </HTML> Листинг 7-6. Файл TestPostLink.aspx, применяемый для тестирования элемента управления PostLink Как видно из листинга 7-5, по окончании загрузки страницы вызывается обработчик события Page_Load. Если переменная IsPostback возвращает True, надпись меняется в соответствии со значениями переменных _EVENTTARGET и _EVENTARGUMENT. При щелчке элемента управления PostLink сервер отсылает браузеру страницу, показанную на рис. 7-3. Элемент управления PostLink передается браузеру в таком виде: <а id="PostLinkControl1" href=" javascript: __ doPostBack( 'Post Lin kCont roll ' ,


Глава 7

268 'This is a test'}"> This is a test

Ш] http: flbcalhostfChapterQT J -ostControl^TestPoslLiik.as_px

*j

"s -л

(

This js.a test Postback from PоstLinkControl I - This is a test

Рис. 7-3. Файл TestPostBack.aspx после щелчка ссылки и изменения сервером ее названия Использование функции Page.GetPostBackClientEvent гарантирует, что браузер получит JavaScript-функцию _doPostBack и что в конечном HTML-файле размещается только одна копия сценария. В клиентских сценариях используются и другие методы класса Page. В следующем разделе описывается наиболее важный из них — метод RegisterClientScriptBlock.

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


Распределение функций между сервером и клиентом

269

ной системы были готовы поступиться некоторыми функциями, но отдельные вещи изменить нельзя. Основная система регистрации пациентов позволяла пользователям вводить данные в формате ддммгг (где дд — день, мм — месяц, а гг — год) или ддммгггг. После ввода даты ее формат изменялся на м/дд/гггг, а при неправильно введенном значении отображалось сообщение о неверной дате. Конечно, можно форматировать дату на стороне сервера и обращаться к нему при каждом изменении данных в текстовом поле даты, но обработка этой информации заметно увеличит трафик между клиентом и сервером. Эту проблему решает создание клиентских сценариев на JavaScript. При создании ASP-страниц, в которых есть выполняемый на стороне клиента код, требуется четко координировать работу дизайнеров страниц и разработчиков JavaScript-сценариев. Технология ASP.NET предлагает лучшее решение — компонент, содержащий в удобном виде все необходимые алгоритмы, легко «перетаскивается» из инструментальной панели прямо на Web-форму. На рис. 7-4 показан элемент управления ReformatDate, который должным образом форматирует дату при выходе из фокуса. if.

i-tt,

Fortes

'^i: '^ J

Щ -Зое*

'•..nnhol/Vebri.

ili»w;5 ; {Г tilp" iVlocalhostJChiiwO5' TsslRsFormatDatS' ZortraKWrtForili 1 .asp.

^j j^SB

-.Ink* "

Рис. 7-4, Элемент управления ReformatDate до и после выхода из фокуса


270

Глава 7

В листинге 7-7 показан полный исходный текст элемента управления ReformatDate. using System; using System.Web.UI;

using System.Web.Ul.WebControls; using System.ComponentModel; using System.Collections.Specialized; [assembly: TagPrefix("Chapter07_ReformatDate","Chapter07")] namespace FormatDateCofitrol {

/// <summary> /// Краткое описание WebCustomControll /// </summary> [DefaultProperty("Text"), ToolboxData("<{0}:ReformatDate runat=server></{0}:ReformatDate>")] public class ReformatDate : System.Web,UI.WebControls.BaseValidator, IPostBackDataHandler, IPostBackEventHandler { private bool blsValid; protected override bool EvaluatelsValidO { this.ServerFormatDate(); return blsValid; \ public ReformatDateC) < b!sValid=true; this.ErrorMessage="*"; 1 [Bindable(true), Category("Appearance"), DefaultValue("")] override public String Text ( get ! return (String) ViewState["Text"];

}

set


Распределение функций между сервером и клиентом

271

ViewState["Text"] = value; !

i

protected override void OnLoad(EventArgs e) {

base.OnLoad(e); if ( Page.IsPostBack )

;

ServerFormatDateO; IsValid=bIsValid;

I if ( Page.ClientTarget.ToLower()!="downlevel" ) { Page. HegisterClientScriptBlock("FormatDateCUentScript", "<" + "SCRIPT Language=JavaScript " + "SRC=\"FormatDate.Js\"></" + "SCRIPT>"); } } protected override void OnInit(EventArgs e) <

: /// <summary> /// Создание текста элемента управления при помощи /// объекта output /// </summary> /// <param name="output"> The HTML writer to write out /// to </param> protected override void Render(HtmlTextWriter output) { if ( Page.ClientTarget.ToLower()!="downlevel" ) { output.Write("<INPUT TYPE=\"TEXT\" ID=" + this.UniquelD + " Name= " + this.UniquelD + " Value=\"" + Text + "\" OnChange=\"FormatDate('" + this.UniquelD + '");\" Size=10 maxlen=1Q>"); } else {

output.Write("<INPUT TYPE=\"TEXT\" ID=" + this.UniquelD + " Name= " + this.UniquelD + " Value=\"" + Text + "\" Size=10 maxlen=10>");


272

Глава 7 this,ControlToValidate=this.UniqueID; output.Write("<span id=val" + this.UniquelD + ">"); if ( IsValid==false } { output.Write("<font color=" + this.ForeColor + ">" + this.ErrorMessage + "</font>"); } output.Write("</span>");

} protected void ServerFormatDateO { string tstr;

System.DateTime dt; b!sValid=false;

tstr=Text; try {

dt=System.DateTime.Parse(tstr); Text=dt.ToShortDateString(); bIsValid=true;

} catch(FormatException fe)

i

if ( tstr.Length==6 || tstr.Length==8 ) < int mo.da.yr; string dtPart; try { dtPart=tstr.Substring(0,2); mo=System.Int32.Parse(dtPart); dtPart=tstr.Substring(2,2); da=System.Int32.Parse(dtPart); dtPart=tstr.Substring(4,tstr.Length-4); yr=System.Int32.Parse(dtPart); if ( yr<30 ) ( yr+=2000; I

else {

if ( yr<100 ) { yr+=1900; i


Распределение функций между сервером и клиентом I Text=mo.ToString() +

"/" + da.ToStringO +

"/" + yr.ToStringO; b!sValid=true;

catch (Exception e) b!sValid=false;

I // Далее следуют функции, связанные // с интерфейсом IPostBackDataHandler public event EventHandler TextChanged; public virtual bool LoadPostData(string postDataKey, NameValueCollection values) i String presentValue = Text; String postedValue = values[postOataKey]; try { if (!presentValue.Equals(postedValue)) ( Text = postedValue; return true; catch ( Exception e ) Text=postedValue; I return false;

} public virtual void RaisePostDataChangedEventO ServerFormatDatef); IsValid=bIsValid; OnTextChanged(EventArgs.Empty); public void HaisePostBackEvent(string EventArgument) 10 Зак. 422

273


274

Глава 7

return; }

protected virtual void OnTextChanged(EventArgs e) { if (TextChanged '= null) Text Changed (this, e); } I

\

Листинг 7-7. WebCustomControl1.cs — исходный текст элемента управления ReformatDate В этом примере я не стал добавлять поддержку для редактора форм Visual Studio .NET, так как вполне подходит отображение элемента по умолчанию в виде текстового поля, содержащего значение свойства Text. (В главе 6 я обг^снил, как добавить поддержку для отображения пользовательского элемента управления в режиме редактирования в редакторе форм.) Метод Render применяется для выполнения абсолютного большинства операций элемента управления ReformatDate на сервере. В этом методе я просто вывожу HTML-код, необходимый для отображения текстового элемента управления. Я определяю идентификатор (ID) текстового элемента управления с помощью метода this.UniquetD (В Visual Basic .NET это Mc.UniquelD), А также определяю атрибуты Size, Max/en и Value. Кроме того, я присваиваю обработчику события OnChange значение JavaScript-функции ForrnatDate, которой с; качестве единственного параметра передается параметр ID элемента управления. Обработчик события OnChange вызывается при выходе из элемента управления, если содержимое последнего изменилось.

Внимание! При создании элемента управления ReformatDate мне не давал покоя вопрос, как он должен отображаться на HTML-странице. В ASP. NET я привык задавать атрибут ID серверных элементов управления и в данном случае совершенно забыл об атрибуте Name. Для правильной пересылки данных с сервера в создаваемом элементе управления необходимо определить оба этих атрибута — и Ю, и


Распределение функций между сервером и клиентом

275

Name. В данном примере (как, впрочем, и всегда) я использовал одинаковые значения атрибутов Ю и Name — значение свойства UniquelD элемента управления. Совет «Тонкий момент» заключается в различии серверных и клиентских событий. В данном примере очевидно, что событие OnChange элемента управления ReformatDate инициируется на стороне клиента, но иногда программисты путаются. Если вы сомневаетесь, правильно ли определены обработчики клиентских событий на серверном элементе управления, всегда можно открыть элемент в Web-браузере и просмотреть исходный текст. Это мощное средство решения проблем с отображением элементов управления. В серверном методе OnLoad элемента управления ReformatDate я сначала посредством base.OnLoad вызываю родительский метод OnLoad. He забудьте, что в Visual Basic .NET эта функция называется MyBase.OnLoad. Поскольку этот класс наследует классу BaseValidator, необходимо вызвать метод родительского класса OnLoad, что позволит компоненту создать сценарий JavaScript, необходимый для элемента управления проверки правильности введенных данных (validator).

Внимание! Применение класса BaseValidator для создания элемента управления ReformatDate —далеко не самый очевидный способ выполнения задачи. Можно попытаться создать составной элемент из двух серверных элементов управления: TextBox и CustomValidator. Такой подход вполне разумен, но, по сути, не так хорош, как наследование от класса BaseValidator (особенно для решения задачи интеграции клиентского кода в элемент управления, что продемонстрировано в примере с ReformatDate). Класс BaseValidator позволяет элементу управления ReformatDate принимать участие в проверке корректности данных на странице.


Глава 7

276

Далее вызывается функция Page.RegisterClientSchptBlock, которая отправляет браузеру клиентский сценарий. Зачем применять этот метод, а не просто отправить сценарий напрямую? Дело в том, что если на странице более одного элемента управления, то нужно обеспечить, чтобы на ней присутствовал лишь один экземпляр сценария. Функция Page.RegisterCHentScriptBlock принимает два параметра: ключ, который используется для уникальной идентификации сценария, и сам сценарий. Обратите внимание, что для включения файла я использую атрибут Src тэга <SCRIPT>, a не вставляю весь сценарий в виде строки. Такой способ позволяет централизовано управлять сценариями, а также корректировать клиентский сценарий без повторной компиляции соответствующего элемента управления, в котором этот сценарий используется. В листинге 7-8 показано содержимое JavaScript-файла FormatDate, js. // Исходный текст на JScript function IsLeapYear(year) ! var blsLeapYear; b!sLeapYear=false; if ( yearH ) { blsLeapYear^true; if С (уеагШО) && I (уеагШО) )

(

bIsLeapYear=false;

}

> return(blsLeapYear); function FormatDate{ControlName) { var Ctrl; var text; var dt; var slash!; var slash2; var loop;


Распределение функций между сервером и клиентом

277

var mo;

var da; var yr; var blsDate; var arrKonthLen=new Array(-1,31,28,31,30,31,30,31,31,30,31,30,31); b!sDate=false; slash1=-1; slash2=-1; ctrl=window.event.srcElement; text=ctrl.value; dt=Date(text); slash"! =text. indexOf ( ' / ' ) ; if ( slash1>=0 ) 1 slash2=text.indexOf('/', slash 1+1); } if ( slash2<0 )

{ if ( text.length==6 || text.length==8 )

{ tstr=text.substring(0,2); mo=parselnt(tstr,10); tstr=text.substring(2, 4); da=parselnt(tstr,10); tstr=text.substring(4,text.length); yr=parselnt(tstr, 10); if ( yrOO ) I yr+=2000; ) else

{ yr+=1900; } if ( isNaN(yr) || isNaN(mo) || isNaN(da) ||

mo<1 || mo>12 || da<1 || da>31 ) i

// неверная дата... I else { if ( mo==2 && isLeapYear(yr) )


Глава 7

278

{

arrMonthLert[2]=29; } if ( da<=arrMonthl_en[mo] ) {

text=mo.toString() + "/" +

da.toStringO + V" + yr.toStringO; window. event. srcElement. value=text; b!sDate=true;

}

:

else

I

bls[)ate=true;

I if ( b!sDate==false 1

!

alert( 'Invalid Date' );

*

return (blsDate);

} Листинг 7-8. Файл FormatDate.js —JavaScript-сценарий, используемый в элементе управления ReformatDate JavaScript-функция FormatDate, показанная на рис. 7-8, выполняет быструю проверку правильности даты независимо от того, использовался ли при наборе слэш (/). Для доступа к содержимому элемента управления я использовал объект window. event. srcElement — он доступен, так как функция FormatDate вызывается как обработчик события. Если введенное значение не содержит двух наклонных черт, дата расценивается как введенная без слэшей. Если строка содержит шесть или восемь символов, она разбивается на части — день, месяц и год. Чтобы получить численное значение (и облегчить последующее форматирование), я использовал JavaScript-функцию parselnt. Интересно, что строку с ведущими нулями она по умолчанию интерпретирует как число в восьмеричной системе исчисления. Таким образом, parselnt('09f) числом не считается, ведь в восьмеричной системе нет цифры «9». К счастью, у функции есть еще второй параметр, в котором передается основание


Распределение функций между сервером и клиентом

279

системы счисления. Вызов функции в форме parseint('09',W) возвращает корректное значение. Если удается определить, что строка, введенная без знаков наклонной черты, скорее всего является датой, она форматируется с использованием этих знаков и значение свойства value элемента window.event.srcElement обновляется. Если дата введена неверно, функция a/erf открывает на стороне клиента информационное окно с соответствующим сообщением. Все это происходит без участия клиента. Примечание Насколько важна возможность изменять JavaScript-файлы? В версии Beta 2 среды ASP.NET содержалась ошибка в JavaScript-файлах. К счастью, ошибочный код не был встроен в элементы управления, и это позволило Microsoft проинструктировать разработчиков, как самостоятельно устранять эту неполадку. В данном примере порядок форматирования даты можно изменить, не корректируя элемент управления, например задать формат ддммгг. Посмотрите еще раз на листинг 7-7. Поскольку класс ReformatDate наследует классу BaseValidator, в нем необходимо реализовать одну функцию —- EvaluatefsValid. Вот как это сделал п: protected override bool EvaluatelsValidO { this.ServerFormatDate();

return blsValid;

> Метод EvaluatekValid возвращает True, если введено корректное значение, и False в противном случае. Метод ServerFormatDate класса ReformatDate выполняет практически ту же проверку, что и JavaScript-функция FormatDate. В общем, если браузер поддерживает JavaScript, некорректная дата никогда не попадет на сервер, — другими словами серверная функция используется в качестве дополнительной линии обороны. Внимание! Функция ServerFormatDate не только выполняет проверку «последней инстанции», когда клиентский браузер не поддерживает JavaScript, но также не позволяет пользователям вводить и от-


Глава 7

280

правлять неверные данные. Не забывайте, что нельзя точно определить, как именно данные попадают на ваш сервер. Поэтому все поступающие от клиента данные следует расценивать как ненадежные, пока не доказано обратное! Элемент управления ReformatDate реализует два интерфейса: IPostBackDataHandler и IPostBackEventHandler. Если нужно информировать элемент управления об обращении клиента к серверу, необходимо реализовать IPostBackEventHandler. В этом интерфейсе необходимо реализовать два метода — LoadPostData и RaisePost Data ChangedEven t: public virtual bool LoadPostData(string postDataKey, NameValueCollection v a l u e s )

(

String presentValue = Text; String postedValue = values[postDataKey]; try (

if (! presentValue. Equals(postedValue)) { Text = postedValue; return true;

catch ( Exception e ) { Text=postedValue; ) return false;

1

public virtual void RaisePostDataChangedEventO < Serve rFormatDateO; IsValid=bIsValid;

OnTextChanged(EventArgs. Empty);

}

В метод LoadPostData передается строковый параметр postDataKey. Он используется в качестве ключа для второго параметра, в данном примере названного values, — объекта NameValueCollection. С помощью postDataKey можно находить значение текущего элемента управления и получать доступ к нему. В этом


Распределение функций между сервером и клиентом

281

примере я присваиваю свойству Text значение, возвращаемое свойством NameValueCollection, если это значение отличается от текущего. Событие RaisePostDataChanged в данном примере вызывает функцию ServerFormatDate, которая устанавливает переменную класса blsValid — это позволяет элементу управления сигнализировать о некорректности своего значения, то есть если его содержимое не удается интерпретировать как дату. V интерфейса IPostBackEventhandler имеется один метод RaisePostBackEvent, который обязательно нужно реализовать. Его применяют для инициирования события обращения к серверу. Метод OnTextChanged вызывает обработчик события TextChange, если последний не пуст (то есть не установлен в Null). TextChange — это обработчик события, который я объявил в данном классе. Клиентская программа может использовать это событие для выполнения некоторых действий при изменении текста. Во многих случаях объявление обработчиков событий и их вызов позволяет пользователям вашего элемента управления управлять его поведением.

Заключение Решение о разнесении функций в разные части приложения всегда дается очень трудно, и в Web ситуация не намного лучше. В ASP.NET выбор языка для программирования серверной части становится несущественным. Можно использовать Visual Basic .NET, C# или любой другой язык, поддержка которого будет обеспечена для платформы .NET. Конечно, даже в этом случае необходимо понимать и учитывать определенные различия между языками, но в целом можно просто программировать на любимом языке. Клиентская часть налагает более жесткие ограничения. На клиенте единственно возможный язык, который поддерживает большинство браузеров, —JavaScript, Это неплохой язык, но зы лишены выбора, Если к тому же вспомнить об отсутствии возможности управления состоянием среды исполнения JavaScript-сценариев на клиенте, станет понятно, что перенесение на клиент слишком большого числа функций по обработке данных — не самое удачное решение.


282

Глава 7

Тем не менее программирование клиента иногда необходимо. Например, на перегруженных Интернет-сайтах перенесение начальной проверки и обработки данных на сторону клиента ускорит взаимодействие пользователя и приложения и снизит нагрузку на сервер. А это уже совсем неплохо. Самые развитые и сложные приложения Интернета и интранета основаны на работе с динамически изменяющейся информацией, получаемой из различных баз данных. В главе 8 я расскажу немного о создании таких приложений. Для получения данных в приложениях ASP.NET используется технология ADO.NET. Пусть это название не сбивает вас с толку — в действительности ADO.NET это очень отдаленный «родственник» технологии ADO (ActiveX Data Objects), а не прямой ее наследник. Способность создавать не просто хорошие приложения, а хорошие приложения, которые к тому же отлично масштабируются, в огромной степени основано на умении применять все новинки ADO.NET.


Глава 8

Одна из наиболее важных задач любого Web-приложения заключается в извлечении и отображении данных. Именно возможность создания управляемого данными, динамического информационного наполнения стало одной из причин огромной популярности технологии ASP. В A5P.NET традиция простого доступа к данным сохранена и приумножена. Помимо обычных механизмов доступа к данным в ASP.NET предусмотрена встроенная всеобъемлющая поддержка XML. И это неудивительно, ведь XML это язык данных в Интернете, a ASP.NET базируется на .МЕТ Framework. Очень странно то, что доступ к базам данных в .NET Framework сильно отличается от механизмов, к которым привыкли программисты, пишущие на ASP и Microsoft Visual Basic 6.0. Технология доступа к базам данных в .NET называется ADO.NET. К сожалению, программистам ASP, привыкшим к ActiveX Data Objects (ADO), n должен сообщить, что ADO.NET— это не просто немного модернизированная «классическая» технология ADO, которую они любят и изучили вдоль и поперек. ADO.NET — это совершенно новый подход к работе с данными. В этой главе я расскажу об XML и объясню, как его применять в своих программах. Кроме того, мы подробно обсудим некоторые различия между ADO и ADO.NET, и вы узнаете, как применять ADO.NET в приложениях ASP.NET.


284

Глава 8

XML как универсальный язык данных В главе 4 я затронул тему XML, рассказывая о конфигурационных файлах Web.config и Machine.config в ASP.NET. Если коротко, то XML — это язык описания и представления данных с помощью простого текста, в последнем он во многом похож на HTML. Есть множество обстоятельств, ставших причиной выбора XML в качестве универсального языка данных. Вспомните, что приходится делать при создании многоуровневого приложения. Когда нужно переслать данные с одного прикладного уровня на другой, решение о способе транспортировки данных зависит от того, где они расположены — на одной или разных машинах. Выбор существенно сужается, если различные уровни приложения располагаются на разных машинах с различными операционными системами. Большинство проблем транспортировки данных удается решить благодаря использованию XML. Допустим, вам нужно переслать информацию заказчику, у которого совершенно другой компьютер и ОС. Вам придется позаботиться об определенном коммуникационном формате, понятном обоим компьютерам. Возможны два решения — использование буфера фиксированной длины или буфера с разделителем полей. Вот примеры первого и второго: REILLY DOUGLAS 1422345819560724DOUG@PROGRAMMINGASP. NET REILLY,DOUGLAS,14223458,19560724,DOUG@PROGRAMMINGASP.NET По виду записи можно частично догадаться, что скрывается за строкой фиксированной длины. В этом примере видно, что «REILLY DOUGLAS» — это имя и фамилия, а в конце скорее всего указан адрес электронной почты. В записи с разделителями имя и адрес электронной почты также хорошо видны, но теперь, когда поля четко выделены, можно заметить, что одно из двух полей содержит дату. Конечно, мы не знаем, что эта за дата, но судя по тому, что речь идет о клиенте, а дата 45-летней давности, то, вероятно, это дата его рождения. Вот другой пример строки с разделителями, но здесь задача идентификации информации намного сложнее: LEE,FRANK,22321234,19920403,YELLOWFISHtPROGRAMMINGASP.NET


Время заняться данными!

285

Как в точности зовут человека — Frank Lee или Lee Frank? Оба варианта возможны. И что это за дата (19920403) — дата рождения очень молодого клиента или дата первой покупки давнего клиента? Для решения задачи недостаточно данных.

Сравнение существующих решений форматирования данных с подходом на основе XML Отдельные компании и отраслевые консорциумы неоднократно предпринимали попытки создать стандартный язык передачи данных. Мне приходилось работать с форматом HL7 (Health Level 7), который применяется для хранения и передачи данных о пациентах посредством одноранговых TCP/IP-подключений. Вот простой пример информации о визите пациента в формате HL7: MSH|"\&|ADT1IMMC|DT$IMMC 120010828131127 ||ЛОТ"А011 Р|2. 3 | <сг> EVN|A01|20010828131127||<сг> PID||100009842491|JONES"BEVERLY"L"||19560214|М||И| 100 PROSPECT ST""LAKEWOOD"NJ"08701|OCEA||||И|104002339191|||9 <сг> NK1|1[JONES-AMY"|В||||||<сг> PV1|1|I1B5"551~A| | | |001218"TEST"DOCTOR—MD| | |HED| |NOF| | | |<cr>

Для понимания сути не нужно знать все подробности визита. Первая строка (MSH) — это заголовок сообщения. Здесь же объявляются разделители, применяемые на различных уровнях сообщения. Следующая строка (EVN) содержит часть сообщения и информирует о событии. В обеих строках речь идет о визите пациента (об этом говорит код А01 в обеих строках). В третьей строке (PID) содержится информация о пациенте. (Для удобства PID разнесена на несколько строк, но на самом деле это одна строка, заканчивающаяся переводом каретки.) Здесь есть такой текст: JJONES~ BEVERLY^ Lл /. Вертикальная черта (|) —это разделитель наивысшего уровня, а «крышечка» (^} используется для разграничения внутри отдельных сегментов. Так как уже известно, что это запись о визите пациента, можно предположить, что это имя пациента. На четвертой строке (NK1) указывается имя ближайшего родственника — Amy. Последняя строка (PV1) содержит информацию, относящуюся к данному конкретному визиту. Все сегменты любых сообщений (а они иногда состоят из сотен строк) тщательно задокументированы, поэтому


286

Глава 8

больницы страны успешно используют стандарт HL7 для передачи информации между системами. Вот та же информация о приеме пациента в формате XML: 123456789 123456789 123456789 123456789 12345 <patientVisit> <admissionType>K/admissionType> <patientLocation> <unit>B5</unit> <room>55K/room> <bed>A</bed> </patientLocation> <doctor> <doctorID>001218</doctorID> <lastName>TEST</lastName> <firstName>OOCTOfi</flrstName> <mix/ml> </doctor> <service>MED</service> <ambulatoryStatus>NOF</ambulatoryStatus> </patientVisit> В предыдущем сообщении формата HL7 почти понятно, зачем нужны данные, но в XML-представлении смыл той же информации абсолютно очевиден. Говорят, что XML является самоописывающим форматом. Конечно, придется приложить определенные усилия на создание программы разбора, но даже через 20 лет, посмотрев на этот файл, вы сможете безошибочно определить что к чему.

Идеален ли XML? Как известно, бесплатным сыр бывает только в мышеловке, за удобство XML приходится платить увеличением нагрузки на систему. В примере формата HL7 информация о визите пациента занимает около 64 байт (1 байт на символ), а в формате XML на это требуется 308 байт. В реальных приложениях разница в размере не так уж важна. На то есть ряд причин. Во-первых, сейчас многим пользователям доступны сети с высокой пропускной способностью, и время на отправку 64 или 308 байт различается несильно. Во-вторых, XML-данные хорошо сжимаются. При нынешнем состоянии технологий разумно сжимать XML-данные при передаче по линиям с ограниченной пропускной способностью или при хранении.


Время заняться данными!

287

Как вы увидите в главе 10, XML является также основой XML Webсервисов. Используемый в качестве языка представления информации, XML позволяет поддерживающим XML приложениям, написанным на разных языках и работающим на разных платформах, успешно взаимодействовать друг с другом. XML критически важен для обработки данных в .NET вообще и в XML Web-сервисах в частности, поэтому вы не ошибетесь, предположив, что в .NET Framework имеются мощные механизмы поддержки этого языка.

Интерфейс lEnumerator Приступая к изучению поддержки различных типов обработки данных в .NET Framework, важно прежде всего познакомиться с особенностями их реализации. Как вы в дальнейшем увидите, в .NET Framework доступна масса объектов. Все варианты доступа независимо от того, расположены ли данные в массиве, в базе данных SQL Server или в XML-документе, имеют одну общую черту — доступ осуществляется через интерфейс /Enumerator. Как вы помните, интерфейс — это определение набора методов и свойств, которые может поддерживать класс. В .NET множественное наследование запрещено, однако в этой среде классу разрешается реализовывать несколько интерфейсов. lEnumerator — это простой интерфейс, содержащий одно свойство и два метода; они перечислены в таблице 8-1. Таблица 8-1.

Элементы-члены интерфейса lEnumerator

Элемент

Описание

Свойство Current Метод MoveNext

Извлекает текущий элемент набора Перемещает перечислитель к следующему элементу набора Устанавливает перечислитель на начальную позицию, перец первым элементом набора

Метод Reset

Реализация этого относительно простого интерфейса позволяет объекту создавать привязку к другим объектам, которые ожидают наличия интерфейса lEnumerator. В Visual Basic 6.0 поддерживалась привязка поля со списком или списка с набором записей (recordset) базы данных. В .NET можно создавать привязку ука-


288

Глава 8

занных элементов управления с чем угодно — от массива до XMLпотока. Для обеспечения доступа к перечислителю (numerator) и поддержки структуры For Each необходимо реализовать интерфейс (Enumerable, у которого есть только один метод (таблица 8-2). Таблица 8-2.

Элементы-члены интерфейса lEnumerabte

Элемент интерфейса

Описание

Метод CetEnumerable

Возвращает экземпляр интерфейса {Enumerator

В листинге 8-1 показана программная логика (CodeBehind-файл} простой страницы. В дополнение к обычному относящемуся к странице классу здесь есть еще один класс с именем МуЕпиmerator. Он реализует оба интерфейса - - (Enumerator и {Enumerable. Using using using using using using using using using using

System; System.Collections; System,ComponentModel; System.Data; System.Drawing; System.Web; System.Web.SessionState; System.Web.UI; System.Web.Ul.WebControls; System,Web.UI.HtmlControls;

namespace ChapterOfl lEnumerator

{

public class MyEnumerator : lEnumerator, Innumerable

i

private int what; private int whatMax; public MyEnumerator() { what=0; whatMax=10; } // Метод lEnumeratable... public lEnumerator GetEnumerator() < return this;


Время заняться данными!

289

// Свойства и методы lEnumerator public object Current

get

return what.ToStringf); public bool MoveNextO if ( what<whatMax) what++; return true; else { return false;

I public void fleset() what=0; id <summary> /// Сводное описание WebForml. /// </summary> public class WebForml : System.Web.UI.Page protected System.Web.UI.WebControls.ListBox ListBoxl; public WebFormK) Page.Init += new System.EventHandler(Page_Init);

I private void Page_Load(object sender, System.EventArgs e) // Установить источник данных // на новый экземпляр MyEnumerator ListBoxl.DataSource=new MyEnumeratorf); ListBoxl.OataBindO; private void Page_Init(object sender, EventArgs e)


Глава 8

290

// CODEGEN: Этот вызов необходим для // ASP. NET Web Form Designer. // InitializeComponent( );

«region Web Form Designer generated code ///

<summary>

/// Этот вызов необходим для Designer, /// Не изменяйте его средствами редактора кода. /// </summary> private void InitializeComponentO I this. Load += new System. EventHandler(this.Page_Load); } fiendregion

Листинг 8-1. CodeBehind-файл страницы, демонстрирующей использование интерфейса lEnumerator Класс MyEnumerator в листинге 8-1 представляет собой простой перечислитель, который возвращает строки с номерами из диапазона 1 —1 0. Две закрытых (private) переменных управляют работой класса. Переменная what содержит текущее, a whatMax — максимальное значение. В свойстве Current части интерфейса lEnumerator используется возможность упаковки типов и вызывается метод ToString для переменной what. Метод MoveNext интерфейса lEnumerator увеличивает значение what, если только оно не больше или равно whatMax. MoveNext возвращает значение типа bool, указывающее на наличие или отсутствие следующего значения. Последний элемент интерфейса I 'Enumerator — метод Reset, который в этом примере устанавливает переменную what в нуль. Реализация /Enumerable проста. Метод GetEnumerator просто возвращает this (в Visual Basic .NET это Me), потому что оба интерфейса /Enumerable и lEnumerator — реализует один объект. Класс программной логики самой страницы похож на примеры в предыдущих главах — различие лишь в методе Page_Load. На


Время заняться данными!

291

самой странице есть список ListBoxl. При загрузке страницы, я выполняю два вызова метода: private void Page_Load(object sender, System. EventArgs e) {

// Установить источник данных // на следующий экземпляр MyEnumerator ListBoxl . DataSource=new MyEnumeratorQ; ListBoxl. DataBindO;

Сначала я установил свойство DataSource в новый экземпляр MyEnumerator. Это возможно, потому что MyEnumerator реализует /Enumerable. Кроме того, разрешается передавать объект, который реализует /Collection. Вызов DataBind заполняет текстовое поле значениями из источника данных (рис. 8-1). .Stow- - %*

Рис. 8-1. Текстовое поле, которое класс MyEnumerator заполняет значениями из источника данных Это пример самого простого использования мощи интерфейсов /Enumerable и /Enumerator. На практике задача решается посредством цикла, который заполняет список элементами. Можно также заполнить массив и создать привязку к нему в методе Page_Load следующим образом:


292

Глава 8

private void Page_Load(object sender, System. EventArgs e) { System. Collections. ArrayList al; al=new System. Collections. ArrayListO; al.Add("One"); al.Add("Two"); al. Add ("Three"); ListBoxl . OataSource=al; ListBox1.0ataBind(); Конечно, в реальности, случается, требуется создать привязку к более сложным объектам, чем простой массив или традиционная база данных. Наличие интерфейсов говорит о том, что возможности подключения к источнику данных ограничены лишь вашим воображением.

Основные сведения об ADO.NET V ASP-программистов есть возможность совершить переход от ASP к ASP. NET сравнительно безболезненно — можно игнорировать CodeBehinel-файлы и продолжать работать, как это делали в ASP. Так вы откажетесь от многих преимуществ ASP. NET, но если требуется перейти на новую технологию с минимальными издержками, придется познакомиться с изменениями в Visual Basic .NET. Переход от ADO к ADO.NET отличается от перехода от ASP к ASP. NET. Хотя в ADO.NET присутствует большинство функциональных возможностей ADO, но на самом деле это принципиально новая технология. Чтобы использовать ADO.NET, необходимо изучить нескольких новых пространств имен, а также несколько возможностей (их немного), которые доступны в ADO, но невозможны в ADO.NET. К счастью, большинству разработчиков (фактически это все ASP.NET-разработчики) они не нужны. Поэтому для начала я познакомлю вас с ADO.

Краткий экскурс в ADO ADO состоит из трех базовых объектов: Connection, Command и Recordset. Первый применяется для создания канала связи между программой и источником данных. Он позволяет устанавливать строку подключения, управлять транзакциями и устанавливать тип курсора. ADO поддерживает серверные и клиентские


Время заняться данными!

293

курсоры, а также управление многими другими свойствами курсора, предназначенными для управления видимостью записей и т. п. Объект Command применяется для выполнения запросов —- произвольных SQL-строк или хранимых процедур. Он поддерживает параметры, что облегчает поддержку передачи значении, которые трудно передавать в обычной строке SQL-кода. Например, взгляните на такую строку: SELECT * FROM Titles WHERE Title='What's Up Doc?

1

Она не выполнится, как ожидается, так как второй апостроф интерпретируется как конец строки, а оставшаяся часть строки отбрасывается как несоответствующая синтаксису. Параметры объекта Command позволяют успешно обрабатывать подобные строки. Объект Recordset применяется для получения информации из источника данных и перемещения по набору записей. В зависимости от типа курсора по набору записей можно перемещаться вперед, назад или в обоих направлениях, кроме того, курсор иногда поддерживает информацию о числе записей. Основной недостаток ADO —сравнительно высокая сложность выполнения операций выбора нужного места курсора, его типа и других параметров. Например, как узнать какой курсор использовать — клиентский или серверный? Какой тип блокировки требуется? Должны ли другие пользователи видеть изменения з наборе записей до того, как вы зафиксируете изменения? Хотя ADO — достаточно гибкая технология, большинству пользователей, особенно в ASP, достаточно сложно ее освоить и научится применять правильно.

Различия между ADO и ADO.NET Посмотрев, что может предложить ADO.NET, программисты, привыкшие к ADO, неизменно восклицают: «Но там слишком много классов!». И это верно. Посмотрев на пространство имен System.Data в .NET Framework, вы увидите, что оно битком набито классами и перечислениями. Сложность в том, что классы разбиты на три различные группы. Одна предназначена для ODBCисточников данных, вторая — для OLE DB, а третья ориентирована на Microsoft SQL Server. Классы этих групп похожи, но не идентичны. На рис. 8-2 показана структура классов в ADO.NET.


294

Глава 8

Примечание На момент написания этих строк ожидается, что ODBC-классы станут доступными как дополнение к .NET Framework. До сих пор неясно, должны ли они поставляться как приложение или как часть .NET Framework. Загрузить поставщик данных ODBC .NET можно со страницы http://www.microsoft. com/do wn to ads/. ADO-программисты наверняка обратят внимание на отсутствие поддержки серверного курсора. Это не ошибка, а конструкторское решение. Два основных объекта позволяют управлять записями. Первый — объект DataSet является копией записей в кэше, расположенном в памяти, его можно просматривать в любом порядке; он похож на статический курсор в ADO. Второй объект, используемый для доступа к данным в ADO.NET, — DataReader. Это хорошо оптимизированное, предназначенное только для чтения средство последовательного просмотра записей, с начала до конца. System. Data

System.Data. Common

System.Data.Gdbe : OdbcCommand QdbcConnection OdbcDataAdapter OdbcDataReader

DataSet IDbCommand IDbConnection IDbDataAdapter IDataReader

System. Dala.OieDb OleDbCommand OleDbConnection OleDbDataAdapter OleDbDataReader

;

'- ystem.Data.SffiCli&nt

SqICommand SqIConnection Sql Data Adapter SqIDataReader

System.Dala.SqiT Рис. 8-2. Иерархия классов ADO.NET


Время заняться данными!

295

Например, одна из стандартных задач Web-приложения заключается в отображении результатов запроса. В подавляющем большинстве Web-приложений никаких записей изменять не требуется. Для таких случаев замечательно подходит DataReader. Объект DataAdapter играет роль посредника между объектом DataSet и источником данных в операциях выборки и сохранения данных. Метод Fill объекта DataAdapter (или SqIDataAdapter в источниках на базе SQL Server) заполняет объект DataSet нужными данными из источника. Метод Update изменяет данные в источнике в соответствии с изменениями в DatsSet, Природа приложений ASP.NET не очень-то подходит к этой модели. Например, данные часто отображаются при первом посещении страницы, а изменения реализуются при отправке формы. В таких обстоятельствах применение одного объекта как для выборки, так и загрузки данных в источник данных дает не слишком много преимуществ. Самое большая сложность в ADO — ограничения при применении СОМ. А наиболее значительное из них — небольшое число поддерживаемых типов данных. Если в наборе записей ADO необходимо разместить объект, его нужно каким-то образом маршалировать как один из типов данных СОМ. В ADO.NET в качестве средства передачи данных применяется XML, поэтому в этой технологии можно воспользоваться способностью самоописания з XML. Кроме того, и это важно для разработчиков корпоративных приложений, транспортировка наборов записей ADO через брандмауэры проблематична, так как большинство брандмауэров сконфигурированы так, что предотвращают СОМ-маршалинг. В ADO. NET результаты без проблем проходят через брандмауэры -— ведь это текстовый протокол, для которого достаточно обычных открытых портов. Многих из вас наверняка заинтересует, какое место объект «набор записей» (recordset) занимает в ADO.NET? В ADO набор записей — это универсальный «швейцарский армейский нож» разработчика базы данных. Нужно получить записи? Пользуйтесь recordset. Удалить записи? Пользуйтесь recordset. Добавить записи? Пользуйтесь recordset. Ну, вы поняли — наборы записей нужны во всех этих ситуациях.


296

Глава 8

Объект DataSet в ADO.NET — самый близкий аналог набора записей в ADO. DataSet позволяет добавлять и обновлять записи. Этот объект также отслеживает состояние всех записей, в том числе вставку, изменение или удаление. В отличие от ADO, где при навигации в пределах набора изменения записи фиксируются, как только курсор «уходит» с нее, в ADO.NET объект DataSet позволяет выполнять изменения по мере необходимости (причем на все время изменений подключение к источнику данных отсутствует) и лишь в конце зафиксировать их. Новая модель доступа к данным «в отключенном состоянии» очень хорошо работает в мире ASP.NET. Забудьте на минутку все свои навыки работы с наборами записей ADO и познакомьтесь с методами получения данных в ADO. NET. Есть определенные различия, но зато ADO.NET позволяет значительно повысить производительность и масштабируемость приложения.

Использование ADO.NET в ASP.NET Многие из наиболее захватывающих достижений в использовании данных в ASP.NET связаны с введением дополнительных серверных элементов управления, которые можно применять в Web-формах. Я расскажу о них в главе 9, а сейчас покажу, как подключаться к источнику данных, извлекать, вставлять и обновлять данные, используя как SQL-операторы, так и хранимые процедуры, Выборка данных В листинге 8-2 показан (Code Be hind-фа ил) пример выборки данных из базы данных Northwind на SQL Server. Оператор выборки выглядит так: SELECT Customer-ID, CompanyName, ContactName FROM Customers WHERE ContactTitle='Owner'

После выборки данных они отображаются на странице в HTMLтаблице. Imports System.Data Imports System.Data.OleDb Public Class WebForml Inherits System.Web.UI.Page Public dr As System.Data.OleDb.OleDbDataReader


Время заняться данными!

297

((Region " Web Form Designer Generated Code " 'Этот вызов необходим для Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeCoroponentQ End Sub

Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System,EventArgs) Handles MyBase.Init 'CODEGEN: Этот вызов необходим для Web Form Designer 'He изменяйте его средствами редактора кода. InitializeComponentQ End Sub tfEnd Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Разместите здесь пользовательский код инициализации 'страницы Dim en As System.Data.OleDb.OleDbConnection Dim cmd As System.Data.OleDb,OleDbCommand en = New 01eDbConnection( _ "Provider=SQLOLEDB;Data Source=localhost;" + "Integrated Security=SSPI;Initial Catalog=Northwind") cn.0pen() cmd = New OleDbCommandf "SELECT CustomerlD,CompanyName.ContactName " + _ "FROM Customers WHERE ContactTitle='Owner'") crod.Connection = en dr = cmd.ExecuteReader(CommandBehavior.CloseConnection) End Sub Private Sub Page_Unload(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Unload dr.CloseO End Sub End Class

Листинг 8-2. CodeBehind-файл SimpleSelect.aspx.vb, который применяется для отображения результатов простого запроса


298

Глава 8

Примечание В этом и некоторых других примерах этой главы строка подключения размещена в CodeBehind-файле. В строках подключения применяется интегрированный механизм безопасности Windows (Integrated Security =SSPf), поэтому имя пользователя и пароль не указываются. Пользователю, в чьем контексте будет выполняться страница, придется предоставить праза доступа к базе данных. В качестве пользователя может выступать конечный пользователь, если применяется олицетворение, или пользователь Internet Information Services (US) (IUSR_<uM#_ машинь»}. Другая альтернатива заключается в использовании раздела appSettings файла Web.config, о котором я рассказывал в главе 4. В листинге 8-3 показана страница, которая отображается в результате запроса выборки. Значения полей CustomerlD, CompanyName и ContactName отображаются в простой HTML-таблице. <Х@ Раде Language="vb" AutoEventWireup="false" Codebehind="SimpleSelect . aspx.vb" Inherits="Chapter08_SimpleData.WebForm1" debug="true"X> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitlonal//EN"> <HTML> <HEAD>

<title></title> <meta name="GENERATOFT content="Microsoft Visual Studio. NET 7.0"> <meta name="CODE_LANGUAGE" content="Visual Basic 7 . 0 " > <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http; //:5chemas. microsoft. com/intellisense/ie5"> </HEAD> <body> <form id="Form1" method="post" rijnat="server"> <P> <FONT face="Verdana" size="4"> <STRONG>Select Data Example </STRONG></FONT>

cp;


Время заняться данными!

299

<table width="600"> <tr bgcolor="#ffff66"> <td>

CustomerlO </td> <td> Company Name </td> <td> Contact/Owner </td> </tr> while dr.Read{) <tr bgcolor="ftffffd7"> <td> <X=dr.GetString(0)!S> </td> <td> <X=dr.GetString<1)s<> </td> <td> <JE=dr.GetString(2)X> </td> </tr> end while </table> </form> </body> </HTML> Листинг 8-3. Страница SimpleSelect.aspx, отображающая данные, полученные в результате работы объекта OleDbDataReader в файле SimpIeSelect.aspx.vb (листинг 8-2)

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


Глава 8

300

На рис. 8-3 показана страница, созданная в результате выполнения кода, который показан в листинге 8-3 и в CodeBehind-файле из листинга 8-2.

шжи „j, -

0 & 3 iS |ГРвп»1*а*

;Д5«жР jsi^ewsIF '& ч\~ у* ^a Ajdf-sie^MltoalB^h^KiSJ.^ePes^.featet.iispx

-j .-*

•*' 1

Select Data Example CustomerlD AHATR АНТОН BQLID BONAP CHOPS DTJMOH FOLKO GROSR LETSS UNOD ОТТЖ

Company Name AnaTnijillo Emparedados у helados Antonio Moreno Taqueria Bdlido Conndas preparadis Bon ^ip'

Chop-suey Chiaese Du moode enber FokochBHB 3RD SELLA-Rc£taurinte Let's Stop H Shop LINO-Delicatese; Ottihe;F.a=eh.in.

CoraactfOwoef Ana Trujflo Anlomo Moreno Martiri SonHoer Laureate Lebhin Yang Wang Janme Labrune ManaLaisson Manuel Pereira Jaime Yorres Feljpe Izquierdo Ht-j-j^.s Pbidieim

I

Hi.,«

Рис. 8-3. Результаты, возвращенные при выполнении кода, показанного в листингах 8-2 и 8-3 Большая часть работы по получению данных выполняется в методе Page_Load {листинг 8-2). Используя локальные объекты OleDbConnection и OleDbCommand, а также открытый объект OleDbDataReader, я выполняю простой запрос данных в базе данных Northwind на SQL Server. Этот пример написан на Visual Basic .NET, но те же объекты .NET Framework используются и в других языках, поддерживаемых .NET.

Совет В ранних версиях .NET Framework применялось отдельное пространство имен для классов, поддерживающих работу с базами данных, отличными от SQL Server. Примеры из других книг или Web-сайтов, в которых применяется System,ado, не станут работать в финальной версии .NET Framework. Процесс доступа к данным в ADO.NET не сильно отличается от того, к чему вы привыкли в ADO. Сначала создается объект OleDbConnection. (Обратите внимание, что в отличие от ADO, используемого в VBScript, для присвоения переменной нового


Время заняться данными!

301

экземпляра не используется оператор Sef.) После создания объекта OleDbCommand я передаю з конструктор SQL-строку. Она выполнится попозже. Я устанавливаю свойство Connection объекта OleDbCommand в объект OleDbConnection, который создается несколькими строками ранее, и, наконец, создаю объект OleDbDataReader, вызывая метод ExecuteReader объекта OleDbCommand следующим образом: dr = cmd.ExecuteReader(CommandBehavior.CloseConnection)

Параметр, передаваемый в ExecuteReader, — это одно из пяти значений перечисления CommandBehavior, которые можно комбинировать побитово. Они перечислены в таблице 8-3. Таблица 8-3. Значения перечисления CommandBehavior Значение

Описание

CloseConnection При выполнении команды соответствующий объектподключение закрывается, если закрыт объект DataReader Keylnfo

SchemaOnly SequentialAccess

SingleResult SingleRow

Запрос возвращает информацию о столбце и основном ключе. Запрос выполняется без блокировки какой бы то ни было из выбранных строк. Когда используется провайдер .NET для SQL Server, он добавляет в оператор выражение FOR BROWSE Запрос возвращает только схему Результаты на уровне столбцов считываются как поток, что позволяет приложению читать большие двоичные значения, применяя методы GetChars или GetBytes Запрос возвращает лишь один результат Ожидается, что запрос возвратит одну строку. Некоторые провайдеры данных .NET используют эту информацию для оптимизации выполнения запроса. Обратите внимание, что во многих случаях использование хранимой процедуры с выходными параметрами оказывается более производительным, если речь идет о запросах, возвращающих лишь одно значение

В этой ситуации CommandBehavior.CloseConnection является идеальным аргументом для передачи, так как объект OleDbConnection создан с применением локального аргумента. При вызове PageJJnload закрытие dr вызовет закрытие подключения, которое этот объект использовал. Есть и другие способы избавить-


302

Глава 8

ся от написания кода закрытия подключения, например превратить объект OleDbConnection в открытую переменную класса. В других ситуациях, например, когда метод класса возвращает объект класса DataReatler, длл закрытия подключения следует отправить CommandBehavior.CloseConnection в метод ЕхесиteReader. Вот конкретный пример. Допустим, у нас есть объект Customer, предоставляющий метод GetCustomer, который возвращает объект класса DataReader. To, что метод вызывает ExecuteReader с CommandBehavior.CloseConnection, означает, что подключение, выделенное и открытое в GetCustomer, будет закрыто при закрытии объекта DataReader. При этом у вас нет доступа к подключению, созданному объектом GetCustomer. Создание запросов на изменение Операции вставки, обновления и удаления в SQL иногда называют запросами на изменение faction query), то есть они выполняют определенные действия, а не простую выборку данных. В ADO.NET их еще называют nonqueries. Код такого запроса похож на код обычной выборки (листинг 8-2). В листинге 8-4 показано содержимое CodeBehind-файла SimpleExecuteNonQuery.aspx.vb, в котором создается простая форма ASP.NET, содержащая три кнопки: Insert, Update и Delete. Imports System.Data Imports System.Data.OleDb Public Class SimpleExecuteNonQuery Inherits System. Web. Ill, Page Protected WithEvents Insert As System.Web.Ul.WebControls. Button Protected WithEvents Update As System.Web.Ul.WebControls. Button Protected WithEvents Delete As System.Web.Ul.WebControls, Button Protected WithEvents Labell As System.Web.Ul.WebControls.Label ttRegion " Web Form Designer Generated Code " 'Этот вызов необходим для Web Form Designer, <System. Diagnostics. I)ebuggerStepThrough()> Private Sub InitializeComponentO


Время заняться данными!

303

End Sub

Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: Этот вызов необходим для Web Form Designer 'He изменяйте его средствами редактора кода. InitializeComponent(} End Sub tfEnd Region Private Sub Page_Load(ByVal sender As System,Object, ByVal e As System.EventArgs) Handles HyBase.Load 'Разместите здесь пользовательский код инициализации страницы End Sub Private Sub Ins6rt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs} Handles Insert.Click Dim en As OleDb.OleDbConnection Dim cmd As OleDb.OleDbComroand en = New 01eDbConnection("Provider=SQLOLEDB;" + "Data Source=localhost;Integrated Security=SSPI;" + "Initial Catalog=Northwind") cn.Openf) cmd = New 01eDbCommand("INSERT INTO " + "Territories(TerritoryID,TerritoryDescription,RegionID) " + _ 11 VALUESC'08724', 'Brick', 3)") cmd.Connection = en Try cmd. ExecuteNonQueryO Catch dbe As System,Data.OleDb.OleDbException Labell.Text = "Exception while Inserting Record! " + dbe.ToStringO End Try

End Sub Private Sub Update_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Update.Click Dim en As OleDb.OleDbConnection Dim cmd As OleDb.OleDbCommand en = New 01eDbConnection("Provider=SQLOLEDB;" + _ "Data Source=localhost;Integrated Security=SSPI;" + _ "Initial Catalog=Northwtnd") en.Open()


304

Глава 8 cmd = New 01eDbCommand("UPDATE Territories " + _ r 'SET TerritoryDescription='Brick Township' " + _ 1 " WHERE TerritoryID= 08724'") cmd.Connection = en Try cmd.ExecuteNonQue ry() Catch dbe As System.Data.OleDb.OleDbException Labell.Text = "Exception while Updating Record! " + _ dbe.ToStringO End Try

End Sub

Private Sub Delete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Delete.Click Dim en As OleDb.OleDbConnection Dim cmd As OleDb.OleDbCommand en = New 01eDbConnection("Provider=SQLQLEDB;" + _ "Data Source=localhost;Integrated Security=SSPI;" + _ "Initial Catalog=Northwind") cn,0pen() cmd = New OleDbCommancK "DELETE FROM Territories " + " WHERE TerritoryID='08724'") cmd.Connection = en Try cmd.ExecuteNonQuery() Catch dbe As System.Data.OleDb.OleDbException Labell.Text = "Exception while Deleting Record! " + dbe.ToStringO End Try

End Sub End Class Листинг 8-4. Файл SimpleExecuteNonQuery.aspx.vb, реализующий команды вставки, обновления и удаления Обработчики событий для всех трех кнопок практически идентичны, а различия — в SQL-коде, передаваемом конструктору объекта OleDbCommand. Запрос вставлен в блок обработки исключений: Try

cmd.ExecuteNonQuery() Catch dbe As System.Data.OleDb.OleDbException Labell.Text = "Exception while Inserting Record! " +


Время заняться данными!

305

dbe.ToStringO End Try Стандартная ошибка, возникающая при вставке данных, — попытка создать дублирующую запись. В этом примере, если щелкнуть кнопку Insert повторно, не нажав после первого щелчка кнопку Delete, вы получите сообщение об ошибке. Вместо стандартного сообщения о необработанном исключении вы увидите информацию, показанную на рис. 8-4. 'lor,.t>»4l .ГН.р1"ПЯ 4t,,il,4-.il I ч г

Exception whJe In.i-!ji«ig Record! Syfleni.Diita.ObPC!.O!?DbExe!iptio-,j The кяжг.гн: :.ss :>'?B tcrjaiaatcd Violation of RbJMARV Г.£'' tac:b-?jrrt '?K_7era:oriei! Cftaet «цел Л'-РЬсае kty e jbjtM Ten-Jvr^s' at System Га:а.О!гОЬ СhDbl'atARssder.NratEesuits(ItliJtjplcIfesuits inuiJiij.;«ReiSi)iJ!:, OlertCisiae; Uoii пяте; lion, Oli-DbCon«!i,KLeJ [•oinni.sndj at System Data OlcDb 0;(DbCommand EHecutcE.saderlntsiEalt'C' =эпэл<ШеЬап%- Ь^а-лы. ;'1гя!^ rnethfd) af Svsiiem Data OlcDb OltDbCoiamsnd EaceciitrMiuiQu-ijC at eEiiffcateNonQueiy bi-B!l...L3jt:is'Cb]« t sfidtr. EveBtAr^s e) in S^'uT^if r'asayain.'le'&^cuffiNonvaery aspx.vb lint- -!0

Рис. 8-4. Информация об исключении, отображенная на странице SimpleExecuteNonQuery.aspx при попытке вставить уже существующую запись В большинстве приложений достаточно отображать лишь часть этой информации. Это осуществимо, так как у объекта O/eDbException много различных свойств, предоставляющих доступ к самым разным элементам информации об ошибках.

Совет Один из способов чуть улучшить расположение отображаемых данных об ошибках заключается в замене символов возврата каретки (которые интерпретируются как свободное пространство на HTML-странице) тэгами <BR>. Замена этой строки: dbe.ToStringO И Зак. 422


306

Глава 8

на эту: dbe.ToString().Replace(Chr(13), "<BR>")

позволит улучшить внешний вид страницы. Использование хранимых процедур Большинство реляционных баз данных поддерживают хранимые процедуры. Хранимые процедуры —это блоки SQL-кода, вызов которых во многом похож на вызов функций в обычных процедурных языках. Хранимые процедуры разбираются и проверяются в момент сохранения, кроме того, план запроса в хранимой процедуре также рассчитывается только раз. Только этих двух особенностей достаточно, чтобы оправдать их существование. Помимо этого можно создать хранимые процедуры для преобразования результатов в удобный формат. Обычно, если требуется получить одну запись, создают запрос, возвращающий ее в виде набора. Однако вы вправе также создать хранимую процедуру, которая возвращает требуемые поля как выходные параметры. Выборка набора даже с одной-единственный записью довольно ресурсоемка, так как в этом случае возвращаются не только сами данные., но и данные о данных (или метаданные]. Использование выходных параметров позволяет значительно повысить производительность. Есть другие причины использования хранимых процедур, например для выполнения некоторых сложных, связанных с данными операций максимально близко к месту хранения этих данных. Манипуляции с данными, требующие выполнения многих SQLоператоров, хранимая процедура обычно выполняет намного эффективнее. В ситуации, когда пришлось бы извлекать большой массив данных на клиентскую машину и выполнять фильтрацию уже на ней, лучше воспользоваться хранимой процедурой. Я прекрасно знаком с «непроцедурной» версией языка Transact SQL в Microsoft SQL Server. Тем не менее в нем есть многие конструкции, такие, как IF и WHILE, которые так нравятся «процедурным» программистам. Однако мощь хранимых процедур базируется на применении в них ориентированного на наборы непроцедурного языка SQL.


Время заняться данными!

307

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

Совет Другой способ решения указанной задачи заключается в создании представления (view) сервера SQL Server с «разрешенными» столбцами таблицы. Пользователям запрещается доступ к основной таблице, а все права управляются через представление. Выполнение хранимых процедур в ADO.NET похоже на выполнение обычных SQL-операторов. В подавляющем большинстве случаев в хранимую процедуру передаются параметры. Хранимая процедура, не принимающая никаких параметров, мало полезна, тем, не менее в приведенном далее примере я применяю именно ее. В следующем примере SimpleSPSelect вызывается простая хранимая процедура по имени «Ten Most Expensive Products» («Десяток самых дорогих товаров»), которая не принимает никаких параметров и возвращает набор записей. Записи считываются в объект DataReader и затем отображаются точно так, как в запросе на выборку в SimpleSelect.aspx.vb (листинг 8-3). Текст SimpleSPSelect.aspx показан D листинге 8-5, а соответствующий CodeBehind-фаил SimpleSPSelect.aspx.cs — в листинге 8-6. <)t@ Page 1апдиаде="с#" Codebehind="SimpleSPSelect.aspx.cs" AutoEventWireup="false" Inherits="Chapter08_SimpleSPSelect.WebForrn1" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <HTHL> <HEAD> <meta narae="GENERATOR" Content="Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" Content="Ctf"> <meta name="vs_defaultClientScript" content="JavaScript (ECMAScript)">


308

Глава 8

<meta name="vs_targetSchema" T content ="http://schemas. microsoft. com/intellisense/ie5 '> </HEAD> <body> <form id="Form1" method="post" runat="server"> <TABLE WIDTH="300" BORDER="0" CELLSPACING="1" CELLPADDINS="1"> <TR bgcolor="#ffff66"> <TD>

Product </TD> <TD> Unit Price </TD> </TR> <* while ( dr.ReadO ) \

%> <TR bgcolor="effffc3"> <TD> <X=dr.GetString(0)j;> </TD> <TD align="right"> <X=dr.GetDecimal(1).ToString("C")X> </TD>

%> <TR>

<TD colspan="2" align="middle"> <asp: Label id="Labe!1" runat="server"> </asp: Label> </TD> </TR> </TABLE> </form> </body> </HTML> Листинг 8-5. Страница SimpleSPSelect.aspx, демонстрирующая отображение данных, возвращенных хранимой процедурой «Ten Most Expensive Products» из базы данных Northwind


Время заняться данными!

309

using System; using System. Collections; using System. ComponentModel; using System. Data; using System. Data. SqlClient; using System. Drawing; using System. Web; using System. Web. SessionState; using System. Web. UI; using System. Web. UI. WebControls; using System. Web. UI.HtmlControls; namespace Chapter08_SimpleSPSelect ( /// <summary> /// Сводное описание WebForml /// </summary> public class WebForml : System. Web. UI. Page { protected System. Web. UI. WebControls. Label Labell; protected System. Data, SqlClient. SqlDataReader dr; public WebForml () { Page.Init += new System. EventHandler(Page_Init); private void Page_Load(object sender, System. EventArgs e) !

// Разместите здесь пользовательский код инициализации // страницы System. Data. SqlClient. SqlConnection en; System. Data. SqlClient. SqlCommand cmd; cn=new SqlConnection("server=localhost; " + "Integrated Security=SSPI; Initial Catalog=Northwind"); cmd=new SqlCommand("Ten Host Expensive Products", en); cmd.CommandType=CommandType.StoredProcedure; try { en. Open(); dr=cmd. ExecuteReader(CommandBehavior.CloseConnection); i catch (System. Data. SqlClient, SqlException sqle) I Labell. Text=sqle.ToString().neplace("\n", "<ВП>"); i


310

Глава 8 private void Page_Unload(object sender, EventArgs e) dr.CloseC); // Здесь также закрывается подключение I

private void Page_Init(object sender, EventArgs e) . //

// CODEGEN: Этот вызов необходим для // ASP.NET Web Form Designer. // InitializeComponentO;

) ffregion Web Form Designer generated code /// <summary> /// Этот метод необходим для поддержки Designer /// не изменяйте содержимое этого метода /// средствами редактора исходного текста. /// </summary> private void InitializeComponentO { this.Load += new System.EventHandler(this.Page_Load); this.Unload += new System.EventHandlerfthis.Page_Unload); } tfendregion '

}

Листинг 8-6. Файл программной логики SimpleSPSelect.aspx.es, демонстрирующий вызов хранимой процедуры в ASP.NET На рис. 8-5 показана страница, полученная в результате выполнения SimpleSPSelect.


Время заняться данными!

311

ш доцппшЕишншшапш Kfe

БЙ

¥еи"

Favorites?

_оЫ!

Help

]БЯ

Г£ре-5опа[ Bar ;Д5вагсЬ ijjFavwftes ^ ^- ^ % e,CS_S,lnPfe5P5dec[,,:lmpleSP5ei,,t.a5p1

Product Cote de Blaye Thuringer Rostbratwurst MishiKobe Niku Sir Rodney's Marmalade Carnarvon Tigers Raclette Courdavault Manjimup Dried Apples Tarte au sucre Ip oh Coffee Rossle Sauerkraut

^J

л

^йо >tl*»-

Unit Price

£263.50 S 123.79 $97.00 $81.00 $62.50 $55.00 $53.00 £49.30 $46.00 £45.60

-d Д|^г*

.i,

, :

Рис. 8-5. Результаты работы SimpleSPSelect.aspx — список самых дорогих товаров, возвращенный хранимой процедурой Следующие две строки з CodeBehind-файле 5impleSPSelect.aspx.cs (листинг 8-6) задают параметры вызова хранимой процедуры; cmd=new SqlCommandC'Ten Host Expensive Products",en); cmd.CommandType=CommandType.StoredProcedure; Конструктору SqICommand передается не SQL-оператор, а имя хранимой процедуры. Затем я устанавливаю свойство CommandType команды в CommandType.StoredProcedure. Как вы помните, в предыдущих примерах этой главы при выполнении SQL-команд я не устанавливал CommandType, так как меня устраивало значение этого свойства по умолчанию. Дальнейшие операции по вызову метода ExecuteReader no отношению к объекту SqICommand и сохранения возвращенного объекта SqlDataReader аналогичны тому, что вы видели в предыдущих примерах.

Примечание Внимательные читатели обратили внимание, что в предыдущих примерах для доступа к данным используется класс OleDbConnection и классы с приставкой OleDb. А в примерах работы с хранимыми процедурами используется класс


312

Глава 8

SqfConnection u классы с приставкой Sqi Различия между этими классами и уместность того или другого класса мы обсудим чуть позже в разделе «Классы SqfCHent и OfeOd». В листинге 8-5 я еще раз воспользовался ASP-подобным способом отображения данных в таблице:

<ТО> <X=dr.GetString(0)j;> </TD> <TD align="right"> <S=dr.GetDecimal(1).ToString("C")Si> </TD> Метод GetString с порядковым числом (нумерация которого начинается с нуля и который представляет собой относительный номер столбца в наборе результатов) вы встречали и раньше, Вторая ячейка таблицы содержит dr.GetDecima!(J).ToString(«C»). Здесь выполняется следующее: извлекается первый столбец данных {второй столбец таблицы) в виде десятичного числа и преобразовывается з строку с помощью метода ToString, з который передается строка с информацией о требуемом формате. В данном случае «С» означает, что значение следует представлять в денежном формате. Еще одно место исходного кода нуждается в комментарии, хотя оно никак не связано с вызовом хранимых процедур. В методе InitializeComponent я добавил одну строку: private void InitializeComponentO ! this.Load +* new System,EventHandler(this.Page_Load); // Следующая строка добавлена мной this.Unload += new System.EventHandlerfthis.Page_Unload); } В дополнительной строке есть оператор + =, он определяет метод PageJJnioad как обработчик события Unload в этой форме. Чтобы в Visual Basic .NET метод использовался для обработки какого-нибудь события в жизненном цикле страницы, к определению метода добавляется строка в следующем синтаксисе Handles MyBase.Unload. Это одно из множества различий между С# и Visual Basic .NET.


Время заняться данными!

313

Примеры хранимых процедур в базе данных Northwind недостаточно наглядно демонстрируют вставку, обновление и удаление строк, поэтому я создал пару собственных хранимых процедур. Первая называется spSaveTerritory, а вторая —spDeleteTerritory. Сценарий их создания показан в листинге 8-7. USE Northwind SET QUOTEO_IDENTIFIER OFF GO SET ANSI_NULLS OFF GO CREATE PROCEDURE spDeleteTerritory @TerritoryID nvarchar(20) AS SET NOCOUNT ON DELETE FROM Territories WHERE TerritoryID=@TerritoryID GO SET QUOTED.IDENTIFIER OFF GO SET ANSI.NULLS ON GO SET QUOTED.IDENTIFIER OFF GO

SET ANSI.NULLS OFF GO

CREATE PROCEDURE SpSaveTerritory giTerritorylD nvarchar(20),

@TerritoryDescription nvarchar(128),

©RegionID

int AS SET NOCOUNT ON DECLARE ©Existing nvarchar(20) SELECT @Existing=TerritoryID FROM Territories WHERE TerritoryID=9TerritoryID IF IsNull(@Existing, " )<>@TerritoryID - Then, INSERT BEGIN INSERT INTO

Territories(ТеrritorylD, TerritoryOescription,RegionID)


Плавав

314

VALUES(@TerritoryID, @TerritoryDescription,§RegionIO) return(1) END ELSE BEGIN UPDATE Territories SET TerritoryDescription=@TerritoryDescription, RegionID=§RegionID WHERE TerritoryID=@TerritoryID return(O) END GO SET QUOTED.IDENTIFIER OFF GO SET ANSI.NULLS ON GO

Листинг 8-7. Хранимые процедуры, выполняющие вставку и удаление региона из базы данных Northwind Обе хранимые процедуры просты для понимания, поэтому л не буду подробно на них останавливаться. Хранимая процедура spSaveTerntory вставляет или обновляет регион, используя поле TerritoryiD для определения того, нет ли уже этого региона в базе данных. При вызове любой из указанных хранимых процедур необходимо задать параметры. V объекта «параметр» есть ряд свойств, наиболее важные из которых перечислены в таблице 8-4. Таблица 8-4. Свойство DhType Direction

ParameterName

Свойства объекта Parameter Описание Tun параметра, определяемый провайдером данных .NET Один из перечислимых типов. Допустимые значения: Input (no умолчанию), Output, tnputOutput и ReturnValue. В хранимой процедуре разрешается одно значение ReturnValue, а на SQL Server ReturnValue всегда относится к типу int Имя параметра. Обратите внимание, что в отличие от ADO, требование по умолчанию (и, насколько я знаю, единственное) заключается в том, что имена параметров должны точно соответствовать имени параметра в SQL-коде или хранимой процедуре


Время заняться данными! Таблица 8-4. Свойство Size

SqlDbType

Value

315

(продолжение) Описание Размер в байтах данных, передаваемых в параметре. Если это свойство опущено, строки слишком большого размера урезаются до разрешенного максимума. Если свойство Size определено и размер строки превышает дозволенный, генерируется исключение Tun SQL. Свойство связанно с DbType. При изменении одного свойства изменяется и другое. В общем случае достаточно определить только одно свойство из двух Получает или устанавливает значение параметра. В параметрах Input или InputOutput очень важно установить свойство Value go выполнения команды

В листинге 8-8 показан файл программной логики SimpleSPActionQueries.aspx.cs. В нем подготавливаются корректные параметры для вызова хранимых процедур spSaveTerritory и spDeleteTerritory. using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Data.SqlClient; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.Ul.WebControls; using System.Web.UI.HtmlControls; namespace SimpleSPActionQueries

{

/// <summary> /// Сводное описание WebForml. /// </summary> public class WebForml : System.Web.UI.Page

(

protected System.Web.Ul.WebControls,Button Save; protected System.Web.Ul.WebControls.Button Delete; protected System,Web.Ul.WebControls.Label Label!; public WebForm1(J


Глава 8

316

Page.Init += new System. EventHandler(Page_Init); private void Page_Load(object sender, System. EventArgs e) { // Разместите здесь пользовательский код // инициализации страницы I private void Page_Init(object sender, EventArgs e) // CODEGEN: Этот вызов необходим для // ASP. NET Web Form Designer.

//

InitializeComponentO;

I

ffregion Web Form Designer generated code /// <summary> /// Метод необходим для поддержки Designer /// Не изменяйте содержимое этого метода /// средствами редактора кода. /// </summary> private void InitializeComponentO

I this. Save. Click += new System. EventHandler(this.Save_CHck); this. Delete. Click += new System. EventHandler (this.Delete_Click); this. Load += new System. EventHandler(this. Page_Load);

I tfendregion private void Save Click(object sender, System. EventArgs e) { System. Data. SqlClient. SqlConnectlon en; System. Data. SqlClient. SqlCommand cmd; System. Data. SqlClient. SqlParameter prm; cn=new SqlConnection("server=localhost ; " + "Integrated Security=SSPI; Initial Catalog=Northwind"); cmd=new SqlCommand("spSaveTerritory",cn}; cmd. CommancfType=CommandType.StoredProcedure; prm=new System. Data. SqlClient. SqlParameter ("§ReturnValue", 3);


Время заняться данными!

317

prm.Direction=ParameterDirection.ReturnValue; cmd.Parameters.Add(prm); cmd.Parameters.Add("®TerritoryID","08724"); cmd.Parameters.Add("@TerritoryDescription","Brick"); cmd.Parameters,Add("@RegionID",3);

try

cn.OpenQ; cmd. ExecuteNonQueryO; Labell.Text="Returned " + cmd.Parameters["@ReturnValue"].Value.ToString(); catch ( System.Data.SqlClient.SqlException sqle ) Labell.Text=sqle.ToString().Replace("\n","<Bfl>"); finally cn.Close(); I private void Delete_Click(object sender, System.EventArgs e) i System.Data.SqlClient.SqlConnection en; System.Data.SqlClient.SqlCommand cmd;

cn=new SqlConnection("server=localhost;" + "Integrated Security=SSPI;Initial Catalog=Northwind"); cmd=new SqlCommand("spDeleteTerrltory",en); cmd.CommandType=CommandType.StoredProcedure; cmd.Parameters.Add("@TerritoryID","08724"); try cn.OpenO; cmd. ExecuteNonQueryO; Labell,Text="Delete Successful"; catch ( System.Data.SqlClient.SqlException sqle ) I Labell.Text=sqle.ToString().Replace("\n","<BR>"); finally I cn.CloseO;


318

Глава 8

Листинг 8-8. CodeBehind-файл SimpleSPActionQueries.aspx.cs, демонстрирующий вызов хранимой процедуры с параметрами Метод $ave_Oick вызывает хранимую процедуру spSaveTerritory, а метод Delete_Click — хранимую процедуру spDeleteTerritory. Обе процедуры требуют передачи параметров, но мы подробно обсудим только метод Save_Click. Как и в предыдущих примерах, SavejClick прежде всего создает объекты SqIConnection и SqlCommand ', а затем — параметры. Есть несколько способов создания параметров. Существует шесть перегрузок коне груктора SqIParameter. Некоторые особенности отдельных версий можно узнать с помощью средства IntelliSense в Visual Studio .NET или в документации no .NET Framework. Каждая перегрузка конструктора требует определенный набор параметров. Кроме того, важно, что при создании параметра можно задать дополнительные его свойства. Например, чтобы создать параметр для обработки значения, возвращаемого хранимой процедурой, я написал следующий код: prm=new System. Data.SqlClient. SqlParameter("@ReturnValue", 3); prm. Direction=ParameterOirection. ReturnValue; cmd. Parameters. Add (prm); Как это обычно случается, нет удобного конструктора, который без проблем позволил бы определить имя параметра ©ReturnValue, его фактическое значение 3 и направление ParameterDirection. ReturnValue. Я взял наиболее подходящий конструктор и установил свойство Direction, так как принятое по умолчанию значение не подходило. Обратите внимание, что я не определяю тип данных ни в этом, ни в остальных примерах этой главы. Поскольку .NET Framework определяет тип передаваемого значения, его не обязательно объявлять явно. Завершив создание параметра, я вызываю метод Add no отношению к набору Parameters объекта SqlCommand. Остальные параметры добавляются в следующем фрагменте; cmd. Parameters. Add ("@TerritoryID", "06724"); cmd. Parameters. Add ("@Ter ri to ryDescript ion", "Brick"); cmd. Parameters. Add("@RegionID", 3);


Время заняться данными!

319

В рассмотренном ранее конструкторе SqlParameter параметр @ReturnValue добавлялся в набор Parameters. Здесь же я вызываю метод Add набора Parameters с именами и значениями трех параметров.

Примечание Помните, что в отличие от ADO, в ADO.NET имя параметра должно в точности соответствовать имени хранимой процедуры. В хранимых процедурах SQL Server также обязательно следует указывать начальный символ «@» в имени переменных. По определению в ADO имена параметров не важны. Порядок добавления параметров в набор Parameters определял ссылки между объектами-параметрами, а имя параметра применялось только для получения выходных параметров. Завершив установку параметров, я вызываю ExecuteNonQuery no отношению к объекту SqICommand, После успешного выполнения я получаю код возврата: Label1.Text="Returned " + cmd. Parameters!;"@ReturnValue"]. Value.ToString(); Я получаю нужный параметр, указывая имя члена набора Parameters. Имейте в виду, что следующий синтаксис неверен: Label1.Text="Returned " •+• cmd.Parameters["@ReturnValue"].ToString(); Эта команда возвратит литерал «@ReturnValue», а не значение 1 (когда запись вставлена) или 0 (когда запись изменена). Во многих областях .NET Framework использование Value позволяет возвратить нужное зам значение, а не некоторое представление имени объекта. Раздел finally структуры обработки исключений применяется для гарантированного закрытия подключения. В группе try/catch/finally код блока try выполняется полностью или лишь до точки, где инициируется исключение. Блок catch выполняется, если сгенерировано исключение корректного типа (как определено в предикате блока catch). И, наконец, блок finally выполняется всегда, что делает его прекрасным местом для освобождения таких ценных ресурсов, как подключения к базе данных.


320

Глава 8

Классы SqlClient и OleDb В примерах в начале этой главы для доступа к данным использовался набор классов в пространства имен OleDb, а в примерах, демонстрирующих использование хранимых процедур, — классы пространства имен SqlClient. В чем же разница? Во многом. Классы OleDb более универсальны. Они позволяют получить доступ к любому поддерживающему OLE DB источнику данных, в том числе к Microsoft SQL Server. Они обеспечивают приемлемую производительность. Трудно определить по отдельности вклад в повышение производительность самой базы данных и улучшений в ASP.NET no сравнению с ASP. Классы SqlClient, как правило, дублируют классы OleDb, но используются исключительно в Microsoft SQL Server. В классе SqIDataReader применяется «родной» формат передачи информации с сервера SQL Server, в котором данные считываются непосредственно с подключения базы данных. Я не выполнял сравнительных тестов на быстродействие этих классов, но по моему субъективному мнению, в большинстве случаев классы SqlClient работают быстрее. Так, как же выбирать семейство классов для своего приложения? Прежде всего, если з качестве СУБД предполагается использовать только Microsoft, SQL Server версии 7.0 или выше, то выбор прост — классы SqlClient. Обращаясь к данным, они пользуются сокращенными путями и поэтому практически всегда работают быстрее классов OleDb. Если же иногда предполагается работать с базой данных Oracle или Jet (Microsoft Access), нужно использовать классы OleDb, no крайней мере для любых источников данных, отличных от Microsoft SQL Server. Хотя логично было бы ожидать, что классы OleDb позволяют получить доступ к любому поддерживающему OLE DB источнику данных, но это не так. В частности, классы OleDb не поддерживают доступ через провайдера OLE DB no протоколу ODBC (Open Database Connectivity). Однако есть отдельный провайдер ODBC .NET (он упомянут ранее в этой глазе), который на момент написания этих строк проходил процедуру раннего бета-тестирования.


Время заняться данными!

321

Как насчет упаковочного класса? Когда возникла необходимость выбора между OteDb и SqICIient, моей естественной реакцией было желание создать класс-упаковку, чтобы во время исполнения программа сама решала, какой из наборов классов использовать. Есть несколько проблем, с которыми следует справиться при решении этой задачи. Прежде всего, .NET Framework не поддерживает множественное наследование. Такая же по важности трудность заключается s том, что и OleDb, и SqICIient герметичные (sealed) классы, то есть им нельзя наследовать. Другой подход заключается в применении композиции, то есть класса, который содержит по экземпляру всех соответствующих классов и во время исполнения решает, какой из них использовать. Однако в этом случае типы данных, поддерживаемые отдельными классами, в точности не совпадают. Практически во всех случаях невозможно отобразить все типы на стандартный набор. Еще одна трудность —упаковать придется огромное количество классов. Наборы классов по большей части совпадают, но не полностью. Например, у OleDbComtnand есть метод Dispose, а у SqICommand его нет. Несмотря на незначительность различий, на упаковку уйдет масса времени.

Преобразование данных в XML-текст Одна из новинок .NET Framework заключается в расширении набора источников данных еще одним — XML-классами. В листинге 8-9 показан код, создающий XML-файл на основании результатов запроса к базе данных. Здесь выполняется выборка всей таблицы Territories из базы данных Northwind, после чего данные выгружаются в формате XML в файл Territories.xml. using using using using using using using using

System; System.Collections; System.ComponentModel; System.10; System.Data; System.Data.SqICIient; System.Drawing; System.Web;


322

Глава 8

using System. Web. SessionState; using System. Web. UI; using System. Web. UI. WebControls; using System. Web. UI. HtmlControls; namespace SimpleXML { /// <summary> /// Сводное описание WebForml. /// </summary> public class WebForml : System, Web. UI. Page I public string xmlStr; public WebForm1() { Page.Init += new System. EventHandler(Page_Init); private void Page_Load(object sender, System. EventArgs e) I // Разместите здесь пользовательский код инициализации // страницы System. Data. SqlClient. SqlConnection en; System. Data. SqlClient. SqlDataAdapter da; System. Data. DataSet ds; System. 10. StreamWriter sr; cn=new SqlConnection("server=localhost; " + "Integrated Security=SSPI; Initial Catalog=Northwind"}; da=new SqlDataAdapter("SELECT * FROM Territories", en); try { en. Open(); ds = new System. Data. DataSetO; da. Fill(ds, "Territories"); xmlStr=ds.GetXtnl(); FileStream fs = new FileStream( "territories.xml", FileMode. OpenOrCreate); fs.SetLength(O); sr=new StreamWriter(fs); sr.Write(xmlStr); sr. Close();

I

catch ( System. Exception sqle ) • sqle.ToString(). Replace ("\n", "<BR>");


Время заняться данными!

323

}

finally

{ }

en. Closet);

}

private void Page_Init(object sender, EventArgs e) // CODEGEN: Этот вызов необходим для // ASP. NET Web Form Designer. // InitializeComponentO;

«region Web Form Designer generated code /// <summary> /// Этот метод необходим для поддержки Designer /// не изменяйте содержимое этого метода /// средствами редактора исходного текста, /// </summary> private void InitializeComponentO { this. Load += new System. EventHandler(this. Page_Load);

\

«endregion

} I Листинг 8-9. Файл SimpleXML.FileSave.aspx.es, выполняющий преобразование данных таблицы Territories базы данных Northwind в формат XML Практически всю работу на этой странице выполняет метод Page_Load, Объект SqIDataSet предоставляет метод CetXml, который возвращает строку набора данных, преобразованную в формат XML Для создания объекта SqIDataSet я использую класс SqlDataAdapter, который выполняет роль посредника между базой данных SQL Server и SqIDataSet. Метод Fill объекта SqlDataAdapter «заселяет» SqIDataSet данными. К готовому SqIDataSet n применяю метод GetXml и сохраняю возвращенное значение в строковой переменной xmlStr. Обратите внимание, что я указал в группе операторов using пространство


324

Глава 8

имен System.IO. В приложениях ASP.NET это пространство имен обычно не используется, однако оно необходимо для записи файлоз. Класс FileStream обеспечивает доступ к файлам. После создания объекта FileStream для XML-файла я создаю новый объект StreamWriter и передаю ему объект FileStream. Если вы посмотрите на иерархию классов, вам наверняка захочется использовать класс TextWriter, но этого сделать нельзя, так как TextWriter абстрактный класс, то есть объект этого класса создать невозможно. Я вызываю метод Write объекта StreamWriter, передавая XML-строку. По завершении работы Write я закрываю объект StreamWriter. Наконец, подключение к базе данных закрывается в блоке finally. Если не указать абсолютный путь, программа запишет XML-файл по относительному пути, используя в качестве исходного каталога System32. Это немного неожиданно, но становится понятным, если вспомнить, что текущий рабочий каталог процесса ASP.NET System 32. На рис. 8-6 показан XML-файл Territories, открытый в Internet Explorer.

- <NewDataSet> - <T~erritories> <TerriloryrD>Q1581</TerritoryID> <TemtoryDescnption>Westboro</TerritoryDescription> <RegionID>l</RegionID> </Territories> - <Temtories> <TemtQry]D>01730</TerntDryID> <TerntoryDescnption>Bedford</TerritoryDescnption> <RegionID>K/RegionID> </Territories> - <Terrjtories> <TemtoryID>P1833</TerntoryID> <TerrituryDes crip *юп> Georgetown-Territory Descriptions «RegionID>l</RegionID> <Aerritones> j

Рис. 8-6. Таблица Territories базы данных Northwind, сохраненная в виде XML-файла в результате работы SimpIeXML.FileSave.aspx.cs


Время заняться данными!

325

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

Заключение Доступ к данным в ASP.NET — это большой шаг вперед по сравнению с ASP. Модель ADO.NET лучше продумана, и хотя она более сложна, но создает прекрасную среду для разработчиков Web-приложений. Классы DataReader обеспечивают быстрый, масштабируемый доступ к данным и годятся для решения практически любых задач доступа к данным в Web, Кроме того, по мере необходимости можно применять более узкоспециализированные и богатые на функции, но менее масштабируемые классы. На первых порах некоторым разработчикам придется несладко из-за отсутствия библиотеки курсоров. Однако в действительности большинство разработчиков никогда в деталях и не понимали значения местоположения курсора и часто делали неоптимальный выбор. Эта глава —лишь краткий экскурс в то, как использовать ADO.NET в ASP.NET. ADO.NET — огромнейшая область, и этой теме, конечно же, посвятят не одну книгу. Из главы 9 вы узнаете, как в ASP.NET осуществляется доступ к данным из Web-форм. В ASP.NET появился ряд серверных элементов управления, которые предоставляют такую легкость отображения данных, о которой вы даже и не мечтали.


Глава 9

Если бы единственными отличиями в работе сданными в ASP.NET были изменения в ADO.NET, на разработчиков ASP они вряд ли произвели большое впечатление. К счастью, в ASP.NET существенно модифицированы процессы обработки отображаемых в ASP.NET-формах данных. Формы в ASP.NET поддерживают дополнительные функции по созданию односторонней привязки с данными в режиме «только для чтения». Как и большинство замечательных новых средств в ASP.NET, основой улучшений в работе с данными являются серверные элементы управления. Универсальным средством, своего рода «швейцарским армейским ножом», в Microsoft Visual Basic 6.0 можно считать элемент управления data grid. Он отображает данные в табличной форме и похож на электронную таблицу. Хорошо это или плохо, но data grid стал основой многих пользовательских интерфейсов, создаваемых в Microsoft Visual Basic 6.0. Такая возможность в ASP.NET вас не разочарует — элемент data grid позволяет воспользоваться всеми преимуществами создания привязки к данным в ASP.NET. Обеспечивая практически все функции стандартной связанной с данными таблицы, data grid в ASP.NET больше похож на Web-интерфейс. Это менее удобно для пользователя, но позволяет более эффективно использовать пропускную способность сети, что особенно важно в Web-приложениях. Существуют и другие серверные элементы управления, менее удобные в работе, но отличающиеся гораздо большей гибкостью. Они позволяют создавать лишь шаблон пользовательского ин-


Данные и формы ASP. NET

327

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

Доступ к данным с применением ASP-форм Возможно, вы обратили внимание, что в ASP устоялся определенный способ работы с данными. Он похож на используемый в примерах главы 8 — сначала создается набор записей, а затем в ASP-файле пишут текст, похожий на этот:

<%

while rs. EOF <> True

%> <TR bgcolor="#ffffc3"> <TD> </TD>

X

<TD align="right"> <)!=rs("Cost")ii!> </TD> </TR> rs. MoveNext Wend

С подобным кодом связан ряд потенциальных проблем. Во-перэых, HTML-код, созданный разработчиком пользовательского интерфейса, смешивается с кодом разработчика баз данных. Это не вызовет трудностей, если обе задачи выполняет один человек, что характерно для небольших сайтов. Однако, когда задача разработки приложения распределена между разработчиками интерфейса, применяющими HTML, и разработчиками программного кода, использующими VBScript (Microsoft Visual BasicScripting Edition), часто возникают затруднения, особенно когда при создании большого приложения разработка пользовательского интерфейса и кода связи с базой данных выполняется одновременно, Этот конфликт можно свести к минимуму, воспользовавшись средством для управления исходным текстом, но оно же может замедлить работу одного из разработчиков, а другому придется работать с единственным файлом с обоими типа кода.


328

Глава 9

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

<х while rs.EOF <> True

К>

<TR bgcolor="#ffffc3"> <TD> <X=rs("Name")i(> </TD> <TD align="right"> <X=rs("Cost")X> </TD> </TR>

<X

Wend

K>

Разница между этим фрагментом кода и предыдущим едва различима и становится очевидной только при отображении страницы: конечно же, речь об отсутствии инструкции перемещения на следующую запись. Из-за этого страница просто не отображается, потому что выражение з операторе while (rs.EOF <> True) всегда обращается в True и программа «зависает» в бесконечном цикле — во всяком случае, пока не остановят сервер Internet Information Services (IIS) или не наступит тайм-аут страницы. ADO. NET не дает забыть оператор перемещения между записями, так как метод Read объекта DataReader не только считывает строку, но и передвигается на следующую запись, возвращая код, информирующий об успехе или неудаче перемещения. Таким образом, пример из главы 8 (где используется С#) применительно к ASP. NET выглядит так: <Х while (dr.ReadO) <TR bgcolor="«ffffc3"> <TD> <*=dr.GetString(0)X> </TD>


Данные и формы ASP. NET

329

<TD aiign="right"> <X=dr.GetDecimal(1).ToString("C")X> </TD> </TR>

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

Совет В предыдущем фрагменте кода я использовал эффективный метод CetString объекта DataReader. Существует множество различных методов Get для получения поддерживаемых типов переменных. Можно также воспользоваться командой dr (<имя_поля>) или dr.CetBooleanfdr.CetOrdinal (<имя_булевого_поля>). В первом случае проблема в том, что возвращаемое значение является объектом и его нужно привести к правильному типу. В рассмотренном примере я уже знаю порядковые номера полей, поэтому, когда вызываю CetString, мне не приходится дополнительно определять названия полей, как того требует команда dr[«FieldName»].

Доступ к данным с использованием c|x>pMASP.NET В этой и следующей главе я проанализирую пример системы по распространению статей о гольфе. Я много работаю для Общества любителей гольфа США и для его печатного издания журнала «Player». Одна из задач, поставленных перед нами, изыскание дополнительных путей предоставления материалов на сайтах наших партнеров. Сейчас, когда партнерам все больше нужны статьи, опубликованные на нашем Web-сайте, они создают ссылки на виртуальные каталоги, размещенные на нем. С точки зрения пользователя эти виртуальные каталоги выглядят, как часть Web-сайта партнера, однако таковыми не являются, поэто-


330

Глава 9

му при каждом изменении партнерами CGOUX сайтов мы вынуждены обновлять и наш Web-сайт, чтобы обеспечить «гладкое» встраивание информации в сайты партнеров. Одним из решений этой проблемы является использование XML Web-сервисов (подробнее о XML Web-сервисах — в главе 10). В этом случае партнеры вправе воспользоваться любым клиентом, поддерживающим протокол SOAP (Simple Object Access Protocol), для подключения к XML Web-cepcucy и запроса статей. При наличии установленного XML Web-сервиса становятся доступны другие возможности. Например, партнеры могут захотеть опубликовать результаты последних турниров по гольфу. XML Webсервис — это превосходное средство предоставления таких материалов наиболее удобным для партнеров способом. Для поддержки подобного XML Web-cepsuca в этой главе мы воспользуемся небольшой базой данных GolfArticles (ее вы найдете на прилагаемом к книге компакт-диске). Кроме статей, в этой базе данных хранится информация о доступе клиентов к той или иной статье.

Серверный элемент управления DataGrid DataGrid— это достаточно гибкий серверный элемент управления, позволяющий создавать табличные формы для отображения, редактирования и удаления строк данных. На рис. 9-1 показана простая табличная форма для отображения некоторых данных из таблицы Customer. Кроме столбцов с информацией (CustomerlD, CompanyName, UserName, Password u DateEntered), в таблице предусмотрены два дополнительных столбца. Первый содержит ссылку Edit для данной строки, а второй — ссылку Delete. В традиционном для Visual Basic 6.0 элементе управления grid, как правило, просто перемещаются между строками и редактируют информацию. Ссылки Edit и Delete используются для переключения в режим редактирования или удаления отдельных строк. При щелчке ссылки Edit в одной из строк DataGrid переходит в режим редактирования строки (рис. 9-2).


Данные и формы ASP.NET

331

Рис. 9-1. ASP.NET-страница в простой табличной форме, созданная в результате работы программы GridTest.aspx

Рис. 9-2. GridTest.aspx со строкой в режиме редактирования Теперь вы можете изменять содержимое во всех столбцах с привязкой к БД, кроме CustomerlD и DateEntered, которые я объявил «только для чтения». В этом примере все еш,е неудовлетворительно выглядят ссылки Edit, Delete, Update и Cancel. Давайте посмотрим, как выглядит эта страница в режиме конструирования в Visual Studio .NET и что можно сделать для ее улучшения. На рис. 9-3 показана страница GridTest.aspx в Visual Studio .NET.


332

Глава 9

ikjlon thaMeiG? btfi.pto & 04*Jtrm_bid

__i! L-gJaiS

Dat 3 bcor_d |C^ •*:'.ш^

.Я**:^ ~*:--*^

LJii С*1ь!й Dal a :',:!, r .; Lfl эЬэиго

!:a;sr(M.--l De-«;o.jni[a-..iL":l:Oa^l:(.jD-alabcu-d OatafccuDaLabraii-c! D^fiioi-

. 1

Eitl'^LsiJS Dar=ibonrc iD-э ,^1'Ю H^tEdit Qfeitia Calsljuuu: !Сч atxi'jnd EJit Pfek-ia Dalaiioiinc |D<a aboijnd Edit 1 Qsjiiis; Dal abound Юв «o'j jnd 1

2

ВЛЯ^Ы^ЫОь'Л

*h Ch»lpW_i.d vrfKO JBOfcBUHa:

DsL^iiOLii'c r^taiic u"

Рис. 9-3. GridTest.aspx в режиме конструирования в Visual Studio .NET

Изменение DataGrid в конструкторе Visual Studio .NET V серверного элемента управления DataGrid имеются сотни свойств, некоторые из них отображаются з окне Properties в нижнем правом углу на рис. 9-3. Конечно же, вы вправе откорректировать эти свойства, используя только окно Properties, однако существуют другие средства для изменения вида и поведения DataGrid. Если щелкнуть правой кнопкой мыши DataGrid в конструкторе, появляется контекстное меню, в котором для нас наиболее интересны команды Auto Format и Property Builder Диалоговое окно Auto Format показано на рис. 9-4. В нем разработчик может изменить десятки параметров, выбрав одну из заранее определенных цветовых схем. Каждому из свойств ItemStyle, AlternatingltemStyle и EditltemStyle сопоставлено множество параметров по управлению цветом, шрифтами, обрамлением и каскадными таблицами стилей. Выбор цветовой схемы вызывает изменение всех стилей одновременно.


Данные и формы ASP.NET

333

i! ijCoiorfull з Colorful 2 rlorfj

з ; { -

Colorful 1 Colorful Б Professional 1 Professional 2 Professional 3 . Simple 1 ; Simple 2 '•. Simple 3 Claisi: 1

Ш

•4I 1 "} i

1 —

##*# ##*# If*** ###t

#### ####

Рис. 9-4. Диалоговое окно Auto Format в Visual Studio .NET Другим полезным средством, облегчающим разработчику работу с элементом управления DataGrid, является средство Property Builder (рис. 9-5), которое позволяет добавлять новые столбцы, изменять текст заголовка, привязку столбцов к полям данных, а также настраивать формат заголовка или содержимого столбцов. Я воспользовался этим средством для выравнивания текста по центру во всех заголовках и в столбце CustomerlD.

Рис. 9-5. Диалоговое окно Property Builder для элемента управления DataGrid


334

Глава 9

Не совсем понятно, как заменить ссылки Edit, Update и Cancel на соответствующие графические изображения. Решение достаточно просто — надо ввести текст соответствующей ссылки в текстовое поле Edit Text. Например, если заменить текст ссылки Edit на строку <!MG SRC=Edit.jpg>, то вместо текста в столбце Edit отобразится рисунок Edit.jpg (рис. 9-6). Если нужно отменить обрамление рисунков, в тэге <1МС> определите атрибут так: Border=0. Для изменения вида рисунка вы можете по своему усмотрению использовать другие атрибуты этого тэга.

Совет Преимущество использования Property Builder для добавления атрибутов в тэги <IMG> по сравнению с использованием текстовых атрибутов, таких, как EditText, заключается в том, что Property Builder при необходимости скорректирует символы, недопустимые в атрибутах. Например, текст <IMG SRC=Edit.jpg> автоматически преобразуется в &lt;IMG SRC=Edit.JPC&gt;.

Рис. 9-6. Страница GridText.aspx с рисунками вместо текстовых ссылок

Примечание Небольшие изображения, которые вы видите на рис, 9-6, — это просто рабочие примеры для демонстрации самой возможности добав-


Данные и формы ASP.NET

335

лять рисунки в табличные формы с данными. Если вы не имеете опыта создания изображений, поступите, как я, — пригласите для этой работы опытного дизайнера. На левой панели диалогового окна Property Builder имеется группа Paging — ее элементы применяются для настройки параметров разбиения на страницы (рис. 9-7). Вы можете указать, нужно ли разбиение на страницы, и если да, то как оно должно выполняться. Можете просто разбить данные на страницы или определить пользовательское разбиение на страницы, а также уточнить, следует ли указывать номера страниц для быстрого перехода или отображать ссылки для перехода на следующую и предыдущую страницу. Вы вправе также ограничить количество записей на одной странице. Если включить разбиение на страницы и не определить пользовательскую разбивку, элемент управления DataCrid самостоятельно выполнит утомительную работу по разбиению на страницы.

•-.-, . •

Рис. 9-7. Группа элементов Paging диалогового окна Property Builder Окончательный вариант содержимого файла С rid Text, a spx показан в листинге 9-1.


336

Глава 9

<Х§ Page Language="vb" AutoEventWireup="false" Codebehind="GridTest.aspx.vb" Inherits="Chapter09_Grid.WebFDrm1"J>> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD>

<tltle></title>

<meta content="Microsoft Visual Studio.NET 7.0" name="GENERATOR"> <neta content="Visual Basic 7.0" name="CODE_LANGUAGE"> <meta content="JavaScript" name="vs_defaijltClientScript"> <meta content="http://schemes.microsoft.com/intellisense/ ie5" name="vs_targetSchema"> </HEAD> <body> <form id="Form1" method="post" runat="server"> <asp:datagrid id="DataGrid1" runat="server" Font-Names="Verdana,Arial" AllowPaging="True" 8orderStyle="None" BorderWidth="1px" BorderColor="ff3366CC" BackColor="White" CellPadding="4" AllowSorting="True" OnDeleteCommand="OnDel6te" DataKeyField="CustomerID" OnUpdateCommand="OnUpdate" OnEditCommand="OnEdit" AutoGenerateColumns="False" OnCancelCommand="OnCancel"> <FooterStyle ForeColor="#003399" BackColor="«99CCCC"> </FooterStyle> <HeaderStyle Font-Bold="True" ForeColor="#CCCCFF" BackColor="«003399"> </HeaderStyle> <PagerStyle NextPageText="Next" PrevPageText="Previous" HorizontalAlign="Left" ForeColor="ft003399" BackColor="»99CCCC" Mode="NumericPages">


Данные и формы A5P.NET

</PagerStyle> <SelectedItemStyle Font-Bold="True" ForeColor="»CCFF99" BackColor="»009999"> </SelectedItemStyle> <EditItemStyle ForeColor="Yellow" BackColor="#99CCCC"> </EditItemStyle> <ItenStyle ForeColor="#003399" BackColor="White"> </ItemStyle> <Columns> <asp: EditCommandColumn ButtonType="LinKButton" UpdateText="&lt;img src=Update. jpg&gt; CancelText="&lt; img src=Cancel. jpg&gt; EditText="&lt;IMG src=Edit. jpg&gt; "> </asp: EditCommandColumn> <asp: ButtonColumn Text="&lt;IMG SRC=DeLete. JPG&gt; " ComfnandNarae=" Delete "> <HeaderStyle HorizontalAlign="Center"> </HeaderStyle> </asp: ButtonColumn> <asp: BoundColumn DataField= "Customer ID" ReadOnly="True" HeaderText="Customer ID"> <HeaderStyle Horizon talAlign="Center"> </HeaderStyle> <ItemStyle HorizontalAlign="Center"> </IternStyle> </asp: BoundColumn> <asp: BoundColumn DataField="CompanyName" HeaderText="Company Name"> <HeaderStyle HorizontalAlign="Center"> </HeaderStyle> </asp: BoundColumn> <азр: BoundColumn DataField="UserName" HeaderText="User Name"> <HeaderStyle HorizontalAlign="Center"> </HeaderStyle> <ItemStyle Width="60px"> </ItemStyle> </asp:BoundColumn> 123ак. 422

337


338

Глава 9

<asp:BoundColumn OataField="Password" HeaderText="Password"> <HeaderStyle HorizontalAlign="Center"> </HeaderStyLe> </asp:BoundColumn> </Columns> </asp:datagrid> </form> </body> </HTML>

Листинг 9-1. Содержимое файла GridText.aspx Все элементы, настроенные средствами Auto Format и Property Builder, присутствуют в окончательном ASPX-коде. Например, эти строки создают в моей табличной форме столбец Customer ID: <asp:BoundColumn DataField="CustomerID" ReadOnly="True" HeaderText="Customer ID"> <HeaderStyle HorizontalAlign="Center"> </HeaderStyle> <ItemStyle HorizontalAlign="Center"> </ItemStyle> </asp:BoundColumr» Атрибуты тэга <asp:BoundColumn> позвол2яют управлять внешним видом и содержанием столбца. Атрибут DataField определяет имя поля в источнике данных, то есть поле, значение которого отображается в столбце. Атрибут HeaderText позволяет определить более понятный заголовок столбца. Столбец CustomerlD предназначен только для чтения, для этого я установил значение атрибута так: Readonly="Тгие". Для настройки внешнего вида заголовка и отдельных элементов применяют тэги HeaderStyle и ItemStyle, Существует возможность также изменять многие другие атрибуты, более подробно об этом рассказано в MSDN. В Visual Studio .NET нет простого («указал и щелкнул») способа настройки обработчиков событий щелчка ссылки Edit, Update, Delete или Cancel (или соответствующих изображений). Необходимый код находится внутри тэга <asp:datagrid> — обратите внимание на три последних атрибута в следующем фрагменте кода;


Данные и формы ASP.NET

339

<asp:datagrid id="DataGrid1" Runat="server" Font-Names="Verdana,Arial" AllowPaging="True" BorderStyle="None" BorderWidth="1px" BorderColor="»3366CC" BaokColor="White" CellPadding="4" AllowSorting="True" AutoGenerateColumns="False" DataKeyField="CustomerID" OnDeleteCommand="OnDelete" OnUpdateCommand="OnUpdate" QnEditCommand="OnEdit" QnCancelCommand="OnCancel">

Этот тэг содержит еще два важных атрибута: AutoGenerateColumns и DataKeyField. Если AutoGenerateColumns установлен в True, столбцы создаются автоматически в соответствии со значением свойства DataSource элемента управления DataGrid. Это решение практически всегда неудачно, поскольку в этом случае значением по умолчанию свойства HeaderText станет имя столбца. В Microsoft SQL Server версии 6.5 и более поздних разрешено использовать пробелы в именах столбцов, например Company Name, однако подобные имена вызывают разного рода проблемы при работе с такими таблицами. Имя столбца CompanyName предпочтительнее для работы SQL Server. Атрибут DataKeyField используется для указания столбца с уникальным ключом в таблице, которую отображает элемент управления DataGrid. После установки значения для этого атрибута намного удобнее в обработчиках событий ссылаться на редактируемую или удаляемую запись.

Модификация DataGrid в Visual Basic .NET В листинге 9-2 показана (файл GridTest.aspx.vb) программная часть (code-behind) пользовательского интерфейса GridTest.aspx. Этот файл отвечает за привязку к базе данных GolfArticles, а также за обновление и удаление записей. Imports System.Data Imports System.Data.SqlCHent


340

Глава 9

Public Class WebForml Inherits System.Web.UI.Page Protected WithEvents OataGridl _ As System.Web.Ul.WebControls.DataGrid Protected en As System.Data.SqlClient.SqlConnection Protected da As System.Data.SqlClient.SqlDataAdapter ttRegion " Web Form Designer Generated Code " 'Этот вызов необходим для Web Form Designer. <System. Diagnostics. DebuggerStepTfirough( )> _ Private Sub InitializeComponentO End Sub

Private Sub Page_Init(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: Этот вызов необходим для 'Web Form Designer 'He изменяйте его средствами редактора кода. InitializeComponentO End Sub #End Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Разместите здесь пользовательский код инициализации страницы If Me.IsPostBack <> True Then doDataBindC) End If End Sub Protected Overridable Sub OnOelete( ByVal Sender As Object, ByVal e As DataGridCommandEventArgs) Dim dr As DataRow Dim item As String Dim cmd As SqlCommand He.cn = New SqlConnection("server=localhost;" + "Integrated Security=SSPI;Initial Catalog=GolfArticles") Try Me.en.Open() item = e.Item.Cells(2).Text cmd = New SqlCommand{ "Delete FROM Customer WHERE CustomerID=" + item, en) cmd. ExecuteNonQueryO


Данные и формы ASP.NET

Catch eDelete As Exception He забываем обработать ошибку Finally cn.CloseQ End Try doDataBindO End Sub Protected Overridable Sub OnEdit(ByVal sender As Object, ByVal e As DataGridCommandEventArgs) DataGridl.Editltemlndex = e.Item.Itemlndex doDataBindO End Sub Protected Overridable Sub OnUpdate( ByVal sender As Object, ByVal e As DataGridcomrnandEventArgs)

Dim Dim Dim Dim

UserName As String password As String companyName As String CustomerlD As String

companyName = Request.Form.Item(1).ToString() UserName = Request.Form.Item(2).ToString() password = Request.Form.Item(3),ToString() Dim cmd As SqlCommand Me.cn = New SqlConnection("server=localhost;" + "Integrated Security=SSPI;Initial Catalog=GolfArticles") Try Me.cn.Open() CustomerlD = e.Item.Cells(2).Text cmd = New SqlCommandC "UPDATE Customer SET CompanyName="' + companyName + T "', UserName= " + UserName + "', Password='" + password + "' WHERE CustomerID=" + CustomerlD, en) cmd. ExecuteNonQueryO Catch eUpdate As Exception ' He забываем обработать ошибку Finally en.Close() End Try

341


342

Глава 9 DataGridl.Editltemlndex = -1

doDataBindO End Sub Protected Overridable Sub OnCancel{ ByVal sender As Object, ByVal e As DataGridCommandEventArgs) DataGridl.Editltemlndex = -1 doDataBindO End Sub 1

Централизованный метод привязки к данным, при необходимости. Protected Sub doDataBindO Dim ds As DataSet Dim be As BoundColumn Me.cn = New SqlConnection{"server=localhost;" + "Integrated Security=SSPI;Initial Catalog=GolfArticles") Me.cn.OpenO He.da = New SqlDataAdapter( "Select * from Customer ORDER BY CompanyName", en) ds = New DataSet("Customers") da.Fill(ds, "Customers") Me.DataGridl.DataSource = _ ds.Таbles("Customers").DefaultView be = New BoundColumnO bc.DataField = "DateEntered" be.HeaderText = "Date Entered" be.ReadOnly = True be.ItemStyle.HorizontalAlign = HorizontalAlign.Center bc.DataFormatString = " { O l d } " Me.DataGridl.Columns.Add(be) Me. DataGridl. DataBindO

End Sub End Class Листинг 9-2.

Файл программной логики GridText.aspx.vb

Обработчик события Page_Loadдовольно прост. Когда страница загружается первый раз (то есть до отправки формы), вызывается метод doDataBind, исходный текст которого показан е конце листинга 9-2. Основная задача этого метода — создание источника данных и установка связи между ним и элементом управления DataCrid, Внимательный читатель заметит, что ни на


Данные и формы ASP.NET

343

рис. 9-7, ни в листинге 9-1 не используется столбец DateEntered. В методе doDataBind показано, как создаются дополнительные столбцы: be = New 8oundCoIumn() be.DataField = "DateEntered" be.HeaderText = "Date Entered" be.ReadOnly = True be. ItemStyle. HorizontalAlign = HorizontalAHgn. Center be.DataFormatString = "{0:d}" He.DataGrldl.Columns.Add(bc) He.DataGridl.DataBindO В этой части исходного текста я впервые создаю новый экземпляр объекта BoundColumn. Я установил значения для некоторых свойств, наиболее интересным из которых является DataFormatString. По умолчанию поля типа дата отображаются в формате мм/ дд/гггг чч:мм:сс ХМ, где мм -- месяц, дд — день, гггг — год, чч —час, мм —минуты,ее —секундыиЛМ —часть суток (AM часть дня до полудня, РМ — пополудни). В большинстве случаев указание даты и времени с точностью до секунды излишне. В данном примере я использую формат строки: {0:d}. Здесь значение 0 указывает, что нужно отображать только дату, a d после двоеточия указывает .NET Framework, что используется короткий формат даты мм/дд/гггг. Обьект BoundColumn — это всего лишь один из множества различных типов столбцов, которые размещают в элементе управления DataCrid. Создав в программе новый столбец, я установил значение свойства ReadOnly в True, так как столбец DataEntered не предполагается изменять. Полный список типов столбцов приведен в таблице 9-1, Таблица 9-1. Типы столбцов, размещаемые в элементе управления DataGrid Тип столбца BoundColumn ButfonColumn

Описание Столбец с привязкой к полю, указанному в свойстве DataSource элемента управления DataCrid Кнопка для элементоо столбца. Позволяет добавлять в столбец настраиваемые кнопки, такие, как кнопка Add (см. след, стр.)


344

Глава 9

Таблица 9-1. (продолжение) Тип столбца

Описание

EditGommandColumn

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

HyperLinkCoIumn

TemplateColumn

После создания объекта столбца и определения значений его свойств я добавляю столбец в набор Columns объекта DataGrid, воспользовавшись методом Add. В конце в doDataBind вызывается метод DataBind объекта DataGrid. У страницы также существует собственный метод DataBind, и в случае использования нескольких элементов управления с привязкой к данным, предпочтительнее применять именно его, а не метод DataBind объекта DataGrid. Это очень важно. Когда страница загружается в первый раз (и когда значение свойства IsPostBack события Page_Load равно False), а также при любом изменении источника данных, происходит переустановка привязки элементов управления к данным. Ни .NET Framework, ни Visual Studio .МЕТнееьшсмня/огэту задачу автоматически. Вы должны сами позаботиться о явной привязке к данным.

Совет Я не пользуюсь визуальными средствами Visual Studio .NET для создания источников данных и подключений. V этих средств есть ряд преимуществ, например создание наборов данных со строгим контролем типов. Недостаток заключается в определенной странности создаваемого ими кода. Например, иногда Visual Studio .NET создает сотни строк исходного текста на SQL, правый край которого «обрезан», а оставшиеся части строк бесцеремонно перенесены на следующую строку, невзирая на названия имен столбцов или ключевых слов SQL.


Данные и формы ASP.NET

345

Получаются вполне корректные SQL-операторы, но такой код чрезвычайно трудно воспринимать, а тем более сопровождать. В примерах, приведенных в этой книге, я не применяю визуальные средства для создания источников данных и подключений. Я считаю, что лучше сразу приложить дополнительные усилия, чем потом править исходный текст. В файле программной логики страницы предусмотрены обработчики событий для всех кнопок. Вот код процедуры OnDelete для кнопки Delete: Protected Overridable Sub OnDeleteC ByVal Sender As Object, ByVal e As DataGridCommandEventArgs) Dim dr As DataRow

Dim item As String Dim cmd As SqlCommand M e . c n = New SqlConnection("server=localhost;" + _

"Integrated Security=SSPI;Initial Catalog=GolfArticles") Try Me.cn.0pen()

Item = e.Item.Cells(2).Text cmd = New SqlCommand{ "Delete FROM Customer WHERE CustomerID=" + item, en) cmd. ExecuteNonQueryO Catch eDelete As Exception 1 He забываем обработать ошибку Finally en.Closet)

End Try

doDataBindO

End Sub Следует отметить, что привязка к данным в ASP.NET выполняется в режиме «только для чтения». Чтобы внести изменения, вам придется внести дополнительный код в программную часть. Прежде всего необходимо установить дополнительное подключение, так же как в примерах главы 8. Далее создается объект SqlCommand в соответствии с оператором SQL. В данном случае мне нужно удалить строку, выделенную в табличной форме. Объект DataGridCommandEventArgs передается обработчику OnDelete в аргументе е. V этого объекта есть свойство Item, у


346

Глава 9

которого з свою есть очередь имеется набор Cells. Набор Cells, отсчет в котором зедегся от нуля, содержит объекты ТаЫеСеН, соответствующие отдельным ячейкам выделенной строки. Используя эту информацию, легко получить значение поля CustomerlD для выделенной строки. Я знаю, что оно находится в третьем столбце, и, таким образом, я ссылаюсь на него как e.ltem. Cell$(2), После этого я создаю SQL-оператор удаления строки для объекта SqICotnmand.

Совет В реальных программах лучше проектировать объекты для создания подключений в подобных описанному классах, в которых есть несколько методов с созданием подключений. Например/ предусмотреть в классе метод GetConnection, который в зависимости от условий возвращал бы объект SqlConnection или OleDbConnection, Развивая этот метод далее, приходим к выводу, что для более крупных систем несомненно целесообразнее изолировать все классы для доступа к данным в отдельном DAL-уровне (Access Data Layer). В мире СОМ этот уровень и US-сервер часто располагают на разных компьютерах. В ASP.NET для этого меньше причин. В общем, несмотря на то, что лично я обычно использую DAL в больших системах, я бы не стал использовать для этого один или несколько отдельных компьютеров. Изучение применения DAL в ASP.NET все еще продолжается, и нет четких рекомендаций для того или другого случая. После настройки объекта SqlCornmand я вызываю метод ExecuteNonQuery (он указывает, что команда не возвращает никаких записей). В заключение я вызвал doDataBind, чтобы отображать на странице последнее представление данных. Это важно, потому что если в процессе обновления изменилось значение поля, по которому отсортированы данные, то их расположение в табличной форме также изменится. Процедура события OnUpdate действует по методу, похожему на OnDelete, только она посылает конструктору SqlCommand SQL-команду обновления, а не удаления.


Данные и формы ASP.NET

347

Примечание С точно таким же успехом я мог бы воспользоваться объектами OleDbConnection, OieDbCommand и связанными с ними. Но так как я работаю с данными, расположенными на SQL Server, разумнее использовать объекты из пространства имен SqlCllent. Оставшиеся два обработчика событий, OnEdit и OnCancel, полностью отличаются от рассмотренных ранее OnDelete и ОпUpdate. Вот исходный текст совсем небольшой процедуры OnEdit: Protected Overridable Sub OnEdit(ByVal sender As Object, ByVal e As DataGridCommandEventArgs) DataGridl, Editltemlndex = e. Item. ItemlncJex doDataBindO End Sub В данной части программы я присваиваю свойству Editltemlndex объекта DataCrid значение свойства Item объекта DataGridCommandEventArgs, которое передается в качестве аргумента обработчику события. В заключение я вызываю функцию doDataBind. Обработчик события OnCancel, которое происходит при выходе из режима редактирования без сохранения изменений, похож на приведенный выше, за исключением того, что значение Editltemlndex устанавливается в «—1», чтобы указать, что нет выделенных элементов. OnUpdate также устанавливает значение Editltemlndex равным «-1» по завершении обновления данных, чтобы после обновления в окне в режиме редактирования не было выделенных строк.

Примечание В коде OnUpdate есть небольшая проблема. Удалось ли вам ее обнаружить? Когда я создаю строку SQL-команды обновления, я, как положено, заключаю строки, отправляемые оператору UPDATE, в одиночные кавычки. Но что произойдет, если в одной из строк уже есть такие символы, например в названии компании «O'Reilly's Golf»? Это может стать причиной ошибки, так как SQL Server распознает в качестве имени компании только пер-


348

Глава 9

вую букву «О» и «споткнется», анализируя оставшуюся часть строки. Одно из решений заключается в замене каждой одиночной кавычки двумя, такие пары внутри строки SQL Server интерпретирует как символ одиночной кавычки. Получше воспользоваться параметрами. Как применять хранимые процедуры и параметры, я покажу в следующем примере.

Серверный элемент управления Repeater DataCr/t/без сомнения удобный элемент управления для отображения результатов поиска или отдельных записей. Однако некоторые недостатки затрудняют его использование в качестве средства для редактирования. Во-первых (и это самое важное), обратите внимание на разницу в общей ширине табличных форм на рис. 9-1 ирис. 9-1. Это небольшая проблема, если у вас немного полей, как в моем примере для DataGrid. С ростом числа полей она становится острее. Другой недостаток заключается в сложности контроля процесса редактирования. Один из способов его устранения — использование объектов TemptateColumn для описания поведения столбца в режиме просмотра и peg актирования. Применение TemplateColumn и настройка свойства EdttltemTemplate имеет смысл, только когда нужно редактировать один-два столбца в записи. При работе с таблицей Customer мы столкнемся с обеими проблемами. Во-первых, в этой таблице присутствуют столбцы с датами и столбец для адреса электронной почты. Они требуют специальной проверки. Так, для поля ContractEnd необходимо убедиться в достоверности даты, а для поля ContactEMail — в правдоподобности адреса электронной почты. Во-вторых, количество столбцов в таблице Customer достаточно велико для отображения на обычной странице. В листинге 9-3 показан SQL-оператор для создания всей таблицы Customer. CREATE TABLE [dbo].[Customer] C [CustomerlD] [int] IDENTITY (1, 1) NOT NULL , [CompanyName] [nvarchar] (50) NOT NULL , [Address] [nvarchar] (50) NULL , [City] [nvarchar] (50) NULL , [State] [nvarchar] (10) NULL ,


Данные и формы ASP.NET

349

[PostalCode] [nvarchar] (20) NULL , [ContactFirstName] [nvarchar] (50) NULL , [ContactLastName] [nvarchar] (50) NULL , [ContactEMail] [nvarchar] (128) NULL , [ContractEnds] [datetime] NOT NULL , [ContractLevel] [int] NOT NULL , [UserName] [nvarchar] (50) NULL , [Password] [nvarchar] (50) NULL , [DateEntered] [datetime] NOT NULL , [DateModified] [datetime] NULL ) ON [PRIHARY] Листинг 9-3. SQL-оператор создания таблицы Customer Даже если бы я переложил управление полями DateEntered и DateModified на базу данных, все равно останется много полей, требующих внимания. Простой просмотр полей в табличной форме может стать проблематичным. К счастью, существуют и альтернативные варианты. На рис. 9-8 показано другое представление данных, созданное с применением серверного элемента управления Repeater.

Рис. 9-8. Отображение данных таблицы Customer с использованием элемента управления Repeater


350

Глава 9

Несомненно, на этой странице представлено больше информации, чем в примере на рис. 9-1. Видны все детали данных (все столбцы таблицы, за исключением автоматически управляемых полей даты). Я изменил шрифт для адресов электронной почты, поскольку ширина строк с адресами больше, чем ширина строк с контактными лицами, имена которых отображаются немного выше в этом же столбце. В первом и втором столбце отображены данные различных полей в более удобном виде. Например, поле CompanyName и связанная с ним информация об адресе компании расположены привычным образом. Несмотря на необходимость дополнительных усилий для обеспечения такой гибкости, преимущество заключается в том, что вы полностью контролируете внешний вид. Настолько же важно то, что совместная работа с дизайнерами HTML-интерфейса окажется намного проще при применении Repeater, чем DataCrid.

Основы работы с элементом управления Repeater Во многих отношениях элемент управления Repeater работает так же, как и DataCrid, но он обеспечивает больше средств детальной настройки столбцов. Нет никаких ограничений на число отображаемых столбцов, кроме того, можно вообще отказаться от табличного представления. Информацию отдельных записей, связанных с элементом управления Repeater, разрешается представлять без какого-либо форматирования, или размечать только тэгами <BR> и <Р>. В большинстве примеров применения Repeater, включая и приведенный здесь пример, для отображения данных на самом деле используются HTML-таблицы. В Repeater отсутствуют некоторые удобства, реализованные в DataGrid, например разбиение на страницы, tfepeater также предоставляет определенные средства обработки событий, однако в данном примере я их не использую.

Совет Элемент управления DataLlst очень похож на Repeater. Он примечателен одной особенностью возможностью создавать многостолбцовые представления данных. Для управления таким представлением изменяют значения свойств RepeatColumns, RepeatDirection и Repeatiayout. Последнее определяет формат отображения: таблица или потоковый вывод.


Данные и формы ASP.NET

351

Элемент управления Repeater поддерживает пятыиаблонов, каждый из которых определяет порядок отображения данных, поступающих в Repeater. Эти шаблоны описаны в таблице 9-2. Как и з DataGrid, в Repeater есть свойство DataSource. Важно ему присвоить значение и убедиться, что элемент управления в состоянии получать данные, в противном случае Repeater не сможет ничего отобразить.

Совет Хотя это и не обязательно, иногда разумно создавать Repeater только с шаблонами HeaderTemplate, ItemTemplate и FooterTemplate. Завершив отладку, особенно это касается ItemTemplate, полученный исходный текст шаблона ItemTemplate копируют в шаблон AtternatingltemTemplate. В противном случае вам придется отлаживать оба шаблона и вносить изменения в исходный текст одновременно это часто становится причиной ошибок. Таблица 9-2. Шаблоны, поддерживаемые элементом управления Repeater Шаблон

Описание

ItemTemplate

Элементы, которые обрабатываются один раз для каждого элемента в источнике данных. В этот шаблон можно добавлять серверные элементы управления Web и HTML Если указано явно, особые элементы обрабатываются этим шаблоном, а не ItemTemplate. Применяется, к примеру, для создания эффекта неактивности (выделение серым цветом) элементов, поддерживающих две цветовые схемы. (На рис. 9-8 AlternatingltemTemplate применяется для вывода одной строки на белом фоне, а следующей — на светло-голубом.) Обычно содержит код для формирования начала таблицы и ее заголовка, Нужно отметить, что, поскольку завершающий элемент таблицы этому тэгу не принадлежит, при работе с элементом управления Repeater в Visual Studio .NET придется использовать отображение в виде HTML (см. след. crp.J

AlternatingltemTemplate

HeaderTemplate


352 Таблица 9-2.

Глава 9 (продолжение)

Шаблон

Описание

FooterTemplate

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

SeparatorTemplate

Текст файла RepeaterTest.aspx показан в листинге 9-4. <%@ Раде 1апдиаде="с#" Codebehind="RepeaterTest.aspx.cs" AutoEventWireup="false" Inherits="Chapter09_Template.WebForm1" *> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD>

<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" Content="C#"> <meta name="vs_defaultClientScript" content="JavaScript (ECMAScript)"> <meta name="vs_targetSchema" content="http://schenias.rfiicrosoft.com/intellisense/ie5"> </HEAD>\ <body> <form id="Form1" method="post" runat="server"> <asp:Repeater id="Repeater1" runat="server"> <HeaderTemplate> <table width="640" bgcolor="ff0033ff"> <tr bgcolor="«0033ff"> <td align="center"> <font face="Verdana,A rial" color="effff99"> <b>Customer</b> </font> </td> <td align="center"> <font face="Verdana,A rial" color="#ffff99">


Данные и формы ASP.NET

<b>Contact Name <br> EMail</b></font> </td> <td align="center"> <font face="Verdana, Arial" color="#ffff99"> <b>User <BR> Name</bx/font> </td> <td align="center"> <font face="Verdana, Arial" color="»ffff99"> <b>Password</b> </font> </td> <td align="center"> <font face=" Verdana, Arial" color="#ffff99"> <b>Contract <br> Ends</b></font> </td> </tr> </HeaderTemplate> <ItemTemplate> <tr bgcolor="*ffffff" width="200"> <td> <font face="Verdana, Arial"> <a href=' EditCu5tomer.aspx?CustomerID= <H DataBinder. Eval(Container. Dataltem, "CustomerlD") X>'> <Xtt DataBinder. EvaKContainer. Dataltem, "CompanyName") !S> <br>

<K# DataBinder. Eval( Container. Dataltem, "Address") %> <br> <%» DataBinder.Eval(Cortainer.DataIteffi, "City") X>, <K# DataBinder. Eval(Container. Dataltem, "State") X>

353


354

Глава 9 Snbsp;Snbsp; <%ti DataBinder.Eval(Container.DataItem, "PostalCode") X> </font> </td> <td> <font face="Verdana,Arial"> <S>» DataBinder.Eval(Container.DataItem, "ContactFirstName") X> <%П DataBinder.Eval(Container.Dataltem, "ContactLastName") %> <br> <font size="1"> <%it DataBinder. Eval(Container. Dataltem, "ContactEMail") %> </fontx/font> </td> <td> <font face="Verdana,Arial"> <Кй DataBinder.Eval(Container.Dataltem, "UserName") X> </font> </td> <td> <font face="Verdana,Arial"> <Xfl DataBinder.Eval(Container.Dataltem, "Password") %> </font> </td> <td> <font face="Verdana,Arial"> <U DataBinder. Eval(Container. Dataltem, "ContractEnds","{0:d}") %> </font> </td> </tr> </ItemTemplate> <AlternatingItemTemplate> <tr bgcolor="e66ccff"> <td> <font face="\/erdana,Arial"> <a href='EditCustomer.aspx?CustomerID= <%$ DataBinder.Eval(Container.Oataltem, "CustomerlD") %>'> <%n DataBinder.EvalfContainer.Dataltem, "CompanyName") K>


Данные и формы ASP.NET

<br> <Х# DataBinder.Eval(Container.Dataltem, "Address") K> <br> <K# DataBinder. Eval(Container. Dataltem, "City") X>, <K# DataBinder. Eval(Container.Dataltem, "State") X> &nbsp;&nbsp; <%# DataBinder.Eval(Container.Dataltem, "PostalCode") %> </font> </td> <td> <font face="Verdana,Arial"> <H DataBinder. EvalfContainer. Dataltem, "ContactFirstName") K> <%П DataBinder.Eval(Container.Dataltem, "ContactLastName") %> <br> <font size="1"> <!S# DataBinder, Eval(Container. Dataltem, "ContactEMail") %> </fontx/font> </td> <td> <font face="Verdana,Arial"> <U DataBinder. Eval(Container. Dataltem, "UserName") *> </font> </td> <td> <font face="Verdana,Arial"> <%H DataBinder.Eval(Container.Dataltem, "Password") %> </font> </td> <td> <font face="Verdana,Arial"> <Xff DataBinder.Eval(Container.Dataltem, "ContractEnds", "{0:dD *> </font> </td> </tr> </AlternatingItemTemplate> <FooterTemplate> <tr>

355


356

Глава 9 <td colspan=5 align=center> <a href="EditCustomer, aspx?CustomerID=0"> <img src="AddNew. jpg" Alt="Add New"></a> </td> </tr> </table> </FooterTemplate> </asp: Repeater>

<asp: Label id="Labe!1" runat="server"x/asp: Label> </forrn> </body> </HTML> Листинг 9-4. Файл RepeaterTest.aspx с более сложным представлением данных таблицы Customer

Внимание! Из-за того что при работе с элементом управления Repeater можно указывать незавершенный HTML-код для отдельных элементов, таких, как начальный тэг < TABLE > без вложенного должным образом завершающего тэга, то страницу с Repeater обычно не удается увидеть в Visual Studio .NET в режиме конструирования (Design). Впрочем, результирующий HTML-код оказывается корректным. Вы вправе редактировать страницы с Repeater в режиме просмотра HTML, но имейте в виду, что по сообщениям об ошибках, появляющимся при переходе в режим конструирования, не всегда удается определить источник неполадки. Исходный текст RepeaterTest.aspx — это довольно простой HTMLкод вперемешку с тэгами ASP. NET. Отличительная особенность этого примера — метод привязки данных к Repeater. Для привязки применяется метод Eval класса DataB'mder. Все связывание с данными происходит с применением разделителей <%# %>,

Примечание Разделители <%# %> используются только для привязки к данным. Выделенное ими выражение вычисляется при каждом вызове Data-


Данные и формы ASP.NET

357

Bind, Этот синтаксис отличается от синтаксиса ASP для отображения переменной, например <%=foo%>. Такие выражения поддерживаются и в ASP.NET, но не используются для привязки к данным. Метод Eval дважды перегружен; обе перегрузки использованы в примере. Синтаксис первой перегрузки таков: [Visual Basic.NET] Overloads Public Shared Function Eval( ByVal container As Object, ByVal expression As String ) As Object [Ct] public static object Eval( object container, string expression ); Аргумент container — это ссылка на объект, с которым сравнивается результат выражения. Во всех случаях использования Eval в листинге 9-4 container — это Container.Data/tern, который ссылается на свойство DataSource элемента управления Repeater. Второй параметр expression — это имя поля в источнике данных. Если вместо объекта DataReader использовать объект DataSet, то орфография будет иметь большое значение, а синтаксис выражения усложнится. Например, синтаксис может выглядеть так: Tabfes[0].DefaultView.[0].CompanyName —вместо более простого CompanyName, так как объекты DataSet ссылаются на несколько таблиц. Вторая перегрузка {дополнительная строка) в методе Eval показана далее: [Visual Basic.NET] Overloads Public Shared Function Eval( ByVal container As Object, ByVal expression As String, _ ByVal format As String ) As String [Of] public static string Eval( object container, string expression, string format


358

Глава 9

Дополнительный параметр format является стандартной строкой в формате .NET Framework. Например, в следующем фрагменте кода в листинге 9-4 используется свойство DataSource элемента управления Repeater, которое получает значение поля ContractEnds и форматирует его как дату: <Х# DataBinder.Eval(Containsг.Dataltem, "ContractEnds","{0:d}") *> Полную информацию о форматах строк вы найдете в MSDN. В листинге 9-4 используются все шаблоны, описанные в таблице 9-2, кроме Separator-Template. Шаблоны ItemTemplate и AlternatingftemTemplate полностью идентичны, за исключением фона в тэгах <TR>. В HeaderTemplate содержатся начальный тэг табличного элемента HTML и строка заголовка. FooterTemplate содержит строку с изображением, что позволяет добавлять как новую запись, так и завершающий тэг таблицы HTML. В RepeaterTest.aspx включены несколько тэгов для создания ссылок на другую форму — EditCustomer.aspx. Подробнее об этой форме мы поговорим далее в разделе «Создание страниц ввода данных».

Совет В самом начале я обратил внимание на одну проблему с RepeaterTest.aspx; Visual Studio .NET преобразовывала код так, что это приводило к неприятностям. Например, между именем и фамилией появлялся лишний пробел. Причина заключается в добавлении ненужного разрыва строки между отрезками кода привязки к БД для имени и фамилии. Возможное решение проблемы заключается в запрете автоформатирования. Для этого в меню Tools выберите команду Options и в открывшемся диалоговом окне Options последовательно перейдите к Text Editor, HTML/XML и Format. На открывшейся странице сбросьте флажки When saving document и Wnen switching from design to HTML/XML view в области Apply Automatic Formatting. Впервые у меня возникли проблемы с автоматическим форматированием, однако сейчас я его отключаю - - так, на всякий случай. В диалоговом окне Options есть и другие параметры, облегчающие работу с тексто-


Данные и формы ASP.NET

359

BbiM редактором, — советую вам познакомиться с ними поближе. В RepeaterTest.aspx также содержится файл программной логики RepeaterTest.aspx.cs, он показан в листинге 9-5. using System;

using using using using using using using using using using

System.Collections; System.ComponentModel; System.Data; System.Data.SqlClient; System.Drawing; System.Web; System.Web.SessionState; System.Web.UI; System.Web.UI.WebControls; System.Web,UI.HtmlControls;

namespace Chapter09_Template 1 /// <summary> /// Сводное описание WebForml. /// </summary> public class WebForm! : System.Web.UI.Page protected System.Web.UI.WebControls.Repeater Repeater!; protected System.Web.UI.WebControls.Label Label!; protected System.Data.SqlClient.SqlCommand cmd; public WebFormlf) Page.Init += new System.EventHandler(Page_Init); private void Page_Load(object sender, System.EventArgs e) i SqlConnection en = new SqlConnection("server=localhost;" + "Integrated Security=SSPI;Initial Catalog=GolfArticles"); SqlCommand cmd; cmd=new SqlCommand( "Select * from Customer Order By CompanyName",en); try cn.OpenQ; Repeater!.DataSource= cmd.ExecuteReaderC CofflmandBehavior.CloseConnection);


360

Глава 9

this.DataBindC); I cstch (System. Exception eLoad) { Labell .Text=eLoad. Message; I private void Page Initfobject sender, EventArgs e) I // // CODEGEN: Этот вызов необходим для // ASP, NET Web Form Designer. // InitializeComponentO; tfregion Web Form Designer generated code /// <summary> /// Этот метод необходим для поддержки Designer /// не изменяйте /// содержимое этого метода средствами /// редактора исходного текста. /// </summary> private void InitializeComponentO I this. Load += new System. EventHandler(this. Page_Load); ftendregion

Листинг 9-5. RepeaterTest.aspx.es — файл программной логики для тестовой страницы класса Repeater В RepeaterTest.aspx.cs содержится единственный метод с кодом, жизненно необходимым для работы Repeater. В методе Page_Load особо важны следующие строки: сп.ОрепО; Repeats r1. DataSource= cmd. E:xecuteReader(

CommandBehavior.ClQseConnection);

this.DataBindO;


Данные и формы ASP.NET

361

Сначала я открываю ранее созданный объект подключения, а затем присваиваю свойству DataSource объекта Repeater значение, возвращенное методом ExecuteReader объекта SqlCommand. Метод ExecuteReader возвращает объект DataReader, и так как я указал CommandBehavior.CloseConnection, подключение закрывается при закрытии объекта data reader. Наконец, я вызвал метод DataBind. Как я уже говорил, без этого вызова не создается привязка к данным и в элементе управления Repeater ничего не отображается. И еще раз хочу напомнить, что ни .NET Framework, ни Visual Studio .NET этот фрагмент исходного кода не вставляют. Очень важно всякий раз, когда связанные данные не отображаются, проверить, предусмотрен ли вызов метода DataBind, и если да, то на корректном ли уровне это происходит.

Использование кэширования для повышения производительности и масштабируемости Одной из особенностей ASP.NET, улучшающих производительность и масштабирование приложений, которые отображают динамические данные, является кэширование выходных данных (output caching). Предположим, в ASPX-файле есть такая строка: <%@ OutputCache Duration="20" VaryByParam="None" %>

При запросе страницы вместо выполнения всего кода она возвращается напрямую из кэша. Длл страниц, генерация которых требует больших затрат и которые редко изменяются, такое кэширование представляет собой огромное преимущество. В этом примере использования директивы OutputCache при запросе страницы в первый раз .NET Framework выполняет весь код, необходимый для создания страницы. При повторном обращении к странице в течение последующих 20 секунд (как указано в атрибуте Duration] сервер возвращает копию из кэша, а не выполняет повторно код. Если же запросить страницу по истечении отведенных 20 секунд, весь код страницы снова будет выполняться. Часто страница вызывается с одним или несколькими параметрами, при этом параметры часто влияют на ее содержа-


362

Глава 9

ние. В этом случае атрибут VaryByParam позволяет выбрать список параметров, разделенных точкой с запятой, или указать «звездочку» {*) для всех параметров — в этом случае в кэше создаются копии страниц для каждого набора параметров. Можно воспользоваться этим же приемом для кэширования фрагмента страницы. Директива OulputCache также применяется в пользовательских элементах управления. Кэширование ASP.NET доступно только на сервере с установленной версией A5P.NET Premium edition.

Создание страниц ввода данных RepeaterTest.aspx предоставляет более удобный способ отображения информации о клиентах в таблице Customer, но редактировать эти данные нельзя. Тем не менее я оформил имя клиента и кнопку Add New как ..?иперссылки. Помните ссылки на EditCustomer.aspx, о которых я говорил в предыдущем разделе? Они позволяют перейти на страницу, показанную на рис. 9-9. ьц^жмцштм11М«р1пмв..П1Ш:^ •. ц .*S«*« Ш ktp; I llQiiilmt,thaijter09_TaiiBbteOMIi;

Customer Maintenance

'_ :,щ^ '

•):

•-•-'-

Я

p. JTesl Customs Contact Name ;ust, First): ] Re illy

C'ty, Stafs sud Zip: [Bloomfield Contract Ends: (1Л/2ШЗ Contact EMail: j do i ug@P год • a .urn in gASP NET User Name: jdoug Password: psl

Рис. 9-9. Страница Customer Maintenance (файл EditCustomer.aspx), позволяющая редактировать записи с информацией о клиентах, выбранные в RepeaterTest.aspx Эта страница позволяет редактировать данные о выбранном клиенте или удалить их. В отличие от традиционных приложений в


Данные и формы ASP.NET

363

Microsoft Access или Visual Basic 6.0, на этой странице нельзя последовательно перемещаться от одной записи к другой. Тем не менее, добавив немного текста, можно создать впечатляющий набор функций. Предположим, я изменил дату ConfractEnds на 1/32/2003 и удалил символ «@»из адреса электронной почты ContactEmail. На рис. 9-10 показан результат этих действий. - £te - I*- ?Лего •.4"£a£fc ' • = £ • ; -

Customer Maintenance

Рис. 9-Ю. Страница Customer Maintenance с указанием полей с неправильно введенными данными — ContractEnds и ContactEmail Как видите, два поля, содержимое которых не прошло проверку, выделены звездочками. Понятно, почему дата неверна, а адрес электронной почты некорректен — отсутствует символ @. Обратите особое внимание на то, что проверка правильности выполняется на стороне клиента, но, даже если клиент не поддерживает JavaScript, серверная часть процедуры проверки все равно выявит эти ошибки. Также очень важно то обстоятельство, что каждое из полей на странице связано по крайней мере с одним элементом управления проверки правильности вводимых данных (validator). Раскрывающийся список State связан с таблицей базы данных, содержащей список штатов и расположенной в той же БД ColfArticles, что и таблица Customer.


364

Глава 9

Создание пользовательского интерфейса Исходный текст EditCustomer.aspx приведен в листинге 9-6. Код этого файла создает страницы, показанные на рис. 9-9 и рис. 9-10. <%@ Page Oebug="true" 1апдиаде="с#"

Codebehind="EditCustomer.aspx. cs" AutoEventWireup="false" Inherits="Chapter09_Template.EditCustomer" X> <!DOCTYPE HTML PUBLIC "-//W3C//DTO HTML 4.0 Transitional//EN" > <HTHL> <HEAD> <META http-equiv=Content-Type content="text/html; charset=windows-1252"> <meta content="Hicrosoft Visual Studio 7.0" name=GENERATOR <meta content=C# narne=CODE_LANGUAGE> <meta content="JavaScript (ECMAScript)" name=vs_defaultClientScript> <meta content=http://schemas.microsoft.com/intellisense/ie5 name=vs_targetSchema> </HEAD> <body> <form id=EditCustoraer method=post runat="server"> <table width=640> <tr> <td colspan=2 align=middle> <pxfont face=Verdana, Arial color=«3300ff size=4> Customer Maintenance </fontx/p> </td> </tr> <tr> <td width="30r' align=right> <font face="Verdana,Arial" size=2 color="#3300ff"> Company Name: </font> </td> <td> <asp:TextBox id=CompanyName runat="server" MaxLength="50" Width="250px" ></asp:TextBox>


Данные и формы ASP.NET <asp:RequiredFieldValidator id=RequiredFieldValidator2 runat="server"

ControlToValidate="CompanyName" ErrorMessage="*"> </asp:RequiredFieldValidator> </td> </tr> <tr> <td width="30iT align=right> <font face="Verdana,Arial" size=2 color="#3300ff"> Contact Name (Last, First): </font> </td> <td> <asp:TextBox id="ContactLastName" runat="server" HaxLength="50" Width="200px" ></asp:TextBox> <asp:RequiredFieldValidator id="Requiredfieldvalidator7" runat="server" ControlToValidate="Contact LastName" ErrorMessage="*"> </asp:RequiredFieldValidator>,&nbsp; <asp:TextBox id="ContactFirstName" runat="server" HaxLength="50" Width="200px" ></asp:TextBox> <asp:RequiredFieldValidator id="Requiredfie!dvalidator8" runat="server" ControlToValidate="ContactFirstName" ErrorMessage="*"> </asp:RequiredFieldValidator? </td> </tr> <tr> <td width="303i" align=right> <font face="Verdana,Arial" size=2 color="#3300ff"> Address: </font>

365


366 </td> <td> <asp:TextBox id="Address" runat="server" MaxLength="50" Width="250px" ></asp:TextBox> <asp:RequiredFieldValidator id=RequiredFieldValidator3 runat="server" ControlToValidate="Address" ErrorHessag6="*"> </asp:RequiredFieldValidator> </td> </tr> <tr> <td width="30X" align=right> <font face="Verdana,Arial" size=2 color="«3300ff"> City, State and Zip: </font> </td> <td> <asp:TextBox id="City" runat="server" MaxLength="50" Width="200px" ></asp:TextBox> <asp:DropDownList id=ddlState runat="server"> </asp:DropDownList> <asp:TextBox id="PostalCode" runat="server" HaxLength="10" Width="70px" ></asp:TextBox> <asp:RequiredFieldValidator id=RequiredFieldValidater4 runat="server" Display="Dynamic" ControlToValidate="City" ErrorMessage="*"> </asp:RequiredFieldValidator>

Глава 9


Данные и формы ASP.NET <asp:RequiredFieldValidator id=RequiredFieldValidator5 runat="server"

Display="Dynamic" ControlToVaUdate="PostalCode" ErrorMessage="*"> </asp:RequiredFieldValidator> <asp:RegularExpressionValidator id=RegularExpressionValidator1 runat="server" CcmtrolToValidate="PostalCode" ErrorMessage="*" ValidationExpression="\d{5}(-\d{4})?"> </asp:flegularExpressionValidator> </td> </tr> <tr> <td width="30X" align=right> <font face="Verdana,Arial" size=2 color="#3300ff"> Contract Ends: </font> </td> <td> <asp:TextBox id="ContractEnds" runat="server" MaxLength="10" Width="70px" ></asp:TextBox> <asp:RequiredFieldValidator id=RequiredFieldValidator1 runat="server" ErrorMessage="*"

ControlToValidate="ContractEnds" Display="Dynamic"> </asp:RequiredFieldValidator> <asp:CompareValidator ID=CompareValidator1 Runat=server ErrorMessage="*" Type=Date Display=Dynamic ControlToValidate="ContractEnds" Qperator="DataTypeCheck"> </asp:CompareValidator> </td>

367


368 </tr> <tr> <td width="30r' align=right> <font face="Verdana,Ariel" size=2 color="(f3300ff"> Contact EMail: </font> </td> <td> <asp:TextBox id="ContactEmail" runat="server" MaxLength="50" Width="250px"

></asp:TextBox> <asp:RequiredFieldValidator id=" Required-field validate гб" runat="server" ControlToValidate="ContactEMail" ErrorMessage="*" Display="Dynamic"> </asp:RequiredFieldValidator> <asp:RegularExpressionValidator id=RegularExpressionValidator2 runat="server" Display="Dynamic" ControlToValidate="Contact Email" ErrorMessage="*" Validation Expression "\w+([-+.]\w+)*@\w+{[-.]\w+)*\.\w+([-. ]\w+)*"> </asp:RegularExpressionValidator> </td> </tr> <tr> <td width="30X" align=right> <font face="Verdana,Arial" size=2 color="ft3300ff"> User Name: </font> </td> <td> <asp:TextBox id="UserName" runat="server" MaxLength="50" Width="250px" ></asp:TextBox> <asp:RequiredFieldValidator

Глава 9


Данные и формы ASP.NET id="Requiredfieldvalidator9" runat="server" ControlToValidate="UserName" ErrorMessage="*"> </asp:RequiredFieldValidator> </td> </tr> <tr> <td widtn="30V' align=right> <font face="Verdana, Arial" size=2 color="ff3300ff "> Password: </font> </td> <td> <asp:TextBox id=" Password" runat="server" HaxLength="50" Width="250px" ></asp:TextBox> <asp: RequiredFieldValidator id="Requiredfieldvalidator10" runat="server" Con trolToValidate=" Password" ErrorMessage="*"> </asp: RequiredFieldValidator> </td> </tr> <tr> <td colspan=2 align=middle> <asp: Button id=BtnSave runat="server" Text="Save"> </asp: Button>Snbsp; <asp: Button id=BtnCancel runat="server" Text="Cancel" CausesValidation="False" > </asp: Button>&nbsp; <asp: Button id=btnDelete runat="server" Text="Delete" Visible="False" CausesValidation=" False "> </asp: Button> </td> . 422

369


370

Глава 9

</tr> </table> <asp:Label id=Labe!1 runat="server" ForeColor="Red" Font-Names="Verdana,A rial"> </asp:Label></form> </body> </HTHL>

Листинг 9-6. Файл EditCustomer.aspx, используемый для создания страницы Customer Maintenance, которая показана на рис. 9-9 и рис. 9-10 Общую структуру EditCustomers.aspx задает HTML-таблица с двумя столбцами. Левый столбец содержит имена полей, а правый элементы управления, предназначенные для ввода и редактирования содержимого полей. Все поля, кроме раскрывающегося списка State, связаны с элементом управления RequiredFieldValidator. Как вы помните, это один из более простых элементов управления проверки правильности. Единственными атрибутами элемента управления RequiredFieldValidator, для которых определены значения, являются ControlToValidate (они указывают на разные элементы управления) и ErrorMessage [в данном случае ему присваивается значение «звездочка» {*)]. V некоторых элементов RequiredFieldValidator, например связанных с текстовым полем ContractEnds, атрибуту Display также присваивается значение Dynamic, В главе 5 я уже говорил, что элементы управления validator, в которых атрибут Display не установлен в Dynamic, занимают место на странице, даже если они вообще не срабатывают. Если в одном элементе управления есть несколько элементов validator с атрибутом Display, установленным в Dynamic, то они не занимают места на странице, пока не сработают. Таким образом, если в одном поле имеются два элемента проверки правильности и срабатывает лишь один из них, то сообщение об ошибке отобразится на одном и том же месте. Несколько элементов управления связаны с элементами управления RegularExpressionValidator. Например, в текстовом поле PostalCode используется следующее объявление RegularExpressionValidator.


Данные и формы ASP.NET

371

<asp:RegularExpressionValidator id=RegularExpressionValidator1 runat="server"

ControlToValidate="PostalCode" ErrorMessage="*"

VaLidationExpression="\d<5}(-\d{4})?"> </asp:RegularExpressionValidator>

Атрибут ValidationExpression указывает на то, что число должно обязательно состоять из пяти цифр, за которыми при необходимости может следовать дефис и еще четыре цифры. Полю ContactEmail также сопоставляется элемент управления RegularExpressionValidator, и его атрибут ValidationExpression выглядит еще сложнее. Я опять рекомендую вам обратиться з MSDN — там синтаксис регулярных выражений описан практически полно и подробно.

Почему в EditCustomers.aspx не используется привязка к данным В листинге 9-6 есть несколько текстовых полей, в том числе поле CompanyName, показанное здесь:

<asp:Text8ox

id=CompanyName

runat="server"

MaxLength="50" Width="250px"> </asp:TextBox> Похоже, что G этом исходном тексте не хватает только кода, связывающего данные с элементами управления. Это не случайно —такова задумка. Начиная работу над этой страницей, я действительно использовал привязку к данным так задавалось текстовое значение элемента управления. Способ работал, но я столкнулся с несколькими проблемами: некоторые из них были очевидны, другие — нет. Во-первых, при декларативной привязке к данным код привязки попадает в ASPX-файл. Это небольшая проблема, и, действительно, в RepeaterTest.aspx есть код привязки вперемешку с кодом пользовательского интерфейса. Объект DataGrid из примера GridTest.aspx служит промежуточным звеном, в котором фактически и объявляется имя поля, свя-


372

Глава 9

зываемого с отдельными столбцами, и вам не нужно применять тэги <%# и %> и обрамляемый ими код привязки. Есть более важная проблема: что происходит, когда вы создаете привязку данных с объектом DataReader и обнаруживаете, что никаких данных нет. Как такое возможно? Причин несколько. Во-первых, свойство CustomerlD передается как параметр в URL-адресе. Пользователь может сделать закладку на странице, создав ссылку с указанием значения CustomerlD. Если при последующем посещении страницы указанное значение CustomerlD уже не существует, неизбежно возникает ошибка. Ошибка вероятна также при попытке добавления нового клиента в список —ссылка +New внизу страницы на рис. 9-8 ссылается на EditCustomer.aspx со значением CustomerlD равным 0 («нуль» означает, что я хочу добавить новую запись). Установка элементов управления текстовых полей вручную в CodeBehind-файле логики работает, и поскольку привязка к данным все равно осуществляется в режиме «только для чтения», то в любом случае нужен код для управления сохранением обновлений всех текстовых полей для любых событий. Последний тип элемента управления проверки правильности вводимых данных — это элемент управления CompareVaiidator. Поле ContractEnds имеет тип даты. И хотя нельзя быть абсолютно уверенным, что это за дата вводимых данных, но точно известно одно — это действительная дата. Задачу решает элемент управления CompareVaiidator с указанием определенного набора атрибутов: <asp:CompareVaiidator ID=CompareVaHdator1 Runat=server ErrorMessage="*" Type=Date

Display=Dynamic

ControlToValidate="ContractEnds" Operator="DataTypeCheck"> </asp:CompareValidator> Самые важные здесь — атрибут Туре (значение — Date), указывающий, что в поле должна содержаться дата, атрибут Control-


Данные и формы ASP.NET

373

ToVah'date (значение — ContractEnds»), указывающий на элемент управления с датой, и атрибут Operator (значение — «DataTypeCheck»), указывающий .NET Framework, что он применяется для проверки вводимых данных. Наконец, внизу страницы расположены несколько кнопок. При создании элементов управления проверки правильности рано или поздно возникает вопрос, что делать, если надо просто уйти со страницы, например, по щелчку кнопки Cancel. В этом случае не нужно требовать от пользователя ввести правильные сведения в поля с проверкой корректности. Достаточно установить атрибут CausesValtdation кнопки Cancel в False. Таким образом, отключится проверка правильности как на клиенте, так и на сервере, позволив обработчику нажатия кнопки на стороне сервера сделать то, что он должен. В нашем примере такой обработчик просто перенаправляет пользователя обратно на страницу RepeaterTest.aspx.

Обработка ввода данных Содержимое файла программной логики EditCustomer.aspx.cs для страницы EditCustomer.aspx, показан в листинге 9-7. Using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; namespace Chapter09_Template I /// <summary> /// Сводное описание EditCustomer. /// </summary> public class EditCustomer : System.Web.UI.Page { protected DropDownList ddlState; protected TextBox CompanyName; protected TextBox Address; protected TextBox City; protected TextBox PostalCode; protected Label Labell;


374

Глава 9

protected TextBox ContractEnds; protected RequiredFieldValidator RequiredFieldValidatorl; protected CompareValidator CompareValidatoM; protected RequiredFieldValidator RequiredFieldValidator2; protected RequiredFieldValidator RequiredFieldValidatorS; protected RequiredFieldValidator RequiredFieldValidator4; protected RequiredFieldValidator RequiredFialdValidatorS; protected RegularExpressionValidator RegularExpressionValidatorl; protected TextBox ContactEmail; protected RequiredFieldValidator Requiredfieldvalidator6; protected RegularExpressionValidator RegularExpressionValidator2; protected TextBox ContactLastName; protected RequiredFieldValidator Requiredfieldvalidator7; protected TextBox ContactFirstName; protected TextBox UserName; protected TextBox Password; protected RequiredFieldValidator flequiredfieldvalidatorB; protected RequiredFieldValidator Requiredfieldvalidator9; protected RequiredFieldValidator RequiredfieldvalidatorlO protected Button BtnSave; protected Button BtnCancel; protected Button btnDelete; protected SqlDataReader dr; public int CustomerlD i get { return Cint)ViewState["CustomerID"]; } set { ViewState["CustomerID"]=value; } I public EditCustomer() { Page.Init += new System,EventHandler(Page Init); i private void doOataBind() { System. Data. SqlCHent.SqlConnection en; System. Data. SqlCHent.SqlConnection cnState; System.Data.SqlCHent.SqlCommand cmd; System. Data.SqlCHent.SqlCommand cmdState; cn=new System.Data.SqlClient.SqlConnection( "server=localhost;" + "Integrated Sec;urity=SSPI;Initial Catalog=GolfArticles") cnState=new System.Data.SqlClient.SqlConnection( "server=localhost;" +


Данные и формы ASP.NET

375

"Integrated Security=SSPI; Initial Catalog=GolfArticles">; cmd=new System. Data. SqlClient. SqlCommand( "spSelectCustomer", en); cmd. CofFimandType=CoinmandType.StoredProcedure; cmd. Parameters. Add ( "©Customs rID", Request. QuerySt ring [ "Gust omerlD"]); cmdState=new System. Data. SqlClient. SqlCommandC "SELECT StateAbbreviation FROH " + "States ORDER BY StateAbbreviation", cnState); try { cn.OpenO; dr=cmd. ExecuteReader( CommandBehavior. CloseConnection); cnState.OpenO; ddlState. DataTextField="StateAbbreviation"; ddlState. DataSource=cmd State. ExecuteReader( CommandBehavior. CloseConnection); if ( dr.ReadO ) ! this.DataBindO; ddlState. Selected! ndex=ddlState. Items. IndexOff ddlState. Items. FindByText (dr. 6etString(4))); CompanyName. Text =(st ring )dr["CompanyName"]; Address. Text=(st ring )dr[ "Address"]; City. Text=(st ring )dr[" City"]; Post alCode.Text=(st ring )dr[" Post alCode"]; ContractEnds.Text= ((DateTime)dr[ "Contract Ends" ]).ToShortDateString(); Cont act Email. Text =(st ring )dr[ "Contact Email"]; Contact First Name. Text =(st ring )dr[ "Contact FirstName"]; Cont act Last Name. Text=(st ring )dr[ "Con tact Last Name"]; Use rName.Text=(st ring )dr["UserName"]; Passwo rd . Text=( st ring )d r[ " Passwo rd " ] ; // Закрыть объект считывания данных и, соответственно, подключение. dr.CloseO; I else I this.DataBindO; catch ( System. Exception eLoad)


376

Глава 9 // Обрабатываем... Label"! .Text=eLoad. Message; btnDelete.Visible=false;

I } private void Page_Load(object sender, System.EventArgs e) // Здесь следует разместить // пользовательский код инициализации страницы if ( l(thls.IsPostBack) ) CustomerID=System.Convert.Tolnt32( (string)Request["CustomerID"]); doDataBindO; if ( CustomerID!=0 } I btnDelete.Visible=true; else 1 btnDelete.Visible=false; } private void Page_Init(object sender, EventArgs e) 1

//

// CODEGEN: Это вызов необходим для // ASP.NET Web Form Designer. // InitializeComponent(); } «region Web Form Designer generated code /// <summary> /// Этот метод необходим для поддержки Designer /// не изменяйте /// содержимое этого метода средствами /// редактора исходного текста. /// </summary> private void InitializeComponentf) ! this.BtnSave.Click +=


Данные и формы ASP.NET

377

new System.EventHandler(this.BtnSave^Click); this.BtnCancel.Click += new System.EventHandler(this.BtnCancel_Click); this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click); this.Load += new System.EventHandler(this.Page_Load);

I «endregion

e)

private void BtnCancel_Click(object sender, System.EventArgs

{

Response.Redirect("RepeaterTest.aspx");

I

private void 8tnSave_Click(object sender, System.EventArgs e) {

System.Data.SqlClient.SqlConnection en; System.Data.SqlClient,SqlCommand cmd; System.Data.SqlClient.SqlParameter prm; if ( this.IsValid } { cn=new System.Data.SqlClient.SqlConnection( "server=localhost;" + "Integrated Security=SSPI;Initial Catalog=GolfArticles"); cmd=new System.Data.SqlClient.SqlCommand( "spSaveCustomer",en); cmd.CommandType=CommandType.StoredProcedure;

try

{

prm=new System.Data.SqlClient.SqlParameter( "@ReturnValue",0); prm.Direction=PararaeterDirection. ReturnValue; cmd.Parameters.Add(prm); cmd.Parameters.Add("§CustomerID",CustomerlD); cmd,Parameters.Add("@CompanyName",CompanyName.Text); cmd,Pa ramete rs.Add{"©Add ress", Add ress. Text); cmd,Parameters.Add("@City",City.Text); cmd,Parameters.Add("@State", ddlState.Selectedltem.Text); cnid.Parameters.Add("@PostalCode",PostalCode.Text); cmd.Parameters.Add("©ContractEnds",


Глава 9

378 System.DateTime.Parse( ContractEnds.Text»; cmd.Parameters.Add("@ContactFirstName", ContactFirstName.Text); cmd.Parameters.Add("@ContactLastName", ContactLastName.Text); cmd.Parameters.Add("@ContactEMail", ContactEmail.Text); cmd.Parameters.Add("@UserName",UserName.Text); cmd.Parameters.Add("©Password",Password.Text); cn.OpenO; cmd. ExecuteNonQueryO; int prmNum; prmNum=cmd.Parameters.IndexOf("@ReturnValue"};

if ( Convert.Tolnt64( cmd.Parameters[prmNum].Value)!=0 ) { Labell.Text="Customer " + cmd.Parameters["@ReturnValue"].Value.ToString(}+ " Saved!"; CustomerID=Convert.ToInt32( cmd. Parameters["@ReturnValue"].Value); // Даем кнопке более внятное имя this. BtnCaricel.Text= "Close"; !

I catch ( System.Exception eSave ) { Label1.Text=eSave.Message; } finally i en.Closet);

private void btnDelete Clickfobject sender, System.EventArgs e) {

System. Data. SqlClient,SqZConnection en; System.Data.SqlClient,SqlCommand cmd; if ( CustomerID!=0 )


Данные и формы ASP. NET

379

cn=new System. Data. SqlClient. SqlConnection( "server=localhost; " + "Integrated Security=SSPI; Initial Catalog=Golf Articles"); cmd=new SqlCommand("spDeleteCustomer", en); cmd. CommandType=CommandType. StoredProcedure;

try { cmd. Parameters. Add С "@CustomerID", CustomerlO); en. Qpen(); cmd. ExecuteNonQueryO; // Даем кнопке более внятное имя. this.BtnCancel.Text="Close"; // Сообщаем о подтверждении. . . Label"! .Text="Customer " + CustomerlD + " Deleted!"; // Больше клиентов нет. . . CustomerID=0; doDataBindO; btnDelete. Visible=true; } catch ( System. Exception eDelete ) i Labell .Text=eDelete. Message; } finally { cn.CloseO;

\ Листинг 9-7. CodeBehind-файл EditCustomer.aspx.es с программной логикой страницы Customer Maintenance Сначала (листинг 9-7) я объявляю свойство с именем CustomerlD, которое сохраняется в составе свойства ViewState, Можно, конечно, воспользоваться скрытым полем формы, как в ASP, но в данном случае применение ViewState предпочтительнее.


380

Глава 9

Следующая интересная часть EditCustomer.aspx.cs — метод doDataBind. Сначала я создаю два объекта типа connection и два объекта типа command. Я одновременно использую результаты обоих объектов command, и поэтому мне нужны два отдельных объекта connection. Еще раз хочу обратить ваше внимание на то, что я использую объекты семейства SqlConnection только потому, что подключаюсь к Microsoft SQL Server. После создания подключений я создаю объекты типа command. В основном подключении к таблице Customer я задаю вызов хранимой процедуры spSelectCustomer, а для подключения к таблице State — команду вызова стандартного SQL-оператора SELECT. После открытия обоих объектов-подключений и вызова ExecuteReader для основного объекта command, сопоставленного таблице Customer (в исходном тексте его имя cmd), я делаю вот что: ddlState.DataTextField*"StateAbbreviation"; ddlState. DataSource=cniclState. ExecuteReader( CommandBehavior.CloseConnection); Свойство DataTextField определяет для раскрывающегося списка State (ddlState}, что StateAbbreviation — поле, которое используется для отображения текста. Таблица States содержит поля StatelD, StateAbbreviation и StateName. В компоненте раскрывающегося списка есть свойство DataValueField, таким образом, я мог бы присваивать этим свойствам различные значения. Если бы я так и поступил, то результирующие тэги <ОРТЮМ> содержали бы значение свойства DataValueField в атрибуте Value, соответствующие всем элементам и значение свойства DataTextField между открывающим (<OPTION>) и закрывающим (</OPTION>) тэгами. Поскольку экранное пространство в этом примере ограничено, отображается только аббревиатура штата и она же является одновременно и текстом, и значением. Следующий код устанавливает выделенный элемент раскрывающегося списка State. if ( dr.ReadO ) {

this.DataBindO; ddlState.SelectedIndex=ddlState.Items.IndexOf( ddlState.Items.FindByText(dr.GetStrlng(4)));


Данные и формы ASP.NET

381

Если dr.Read возвращает true, я вызываю метод DataBind и затем устанавливаю выбранный элемент раскрывающегося списка ddlState. Этот элемент должен устанавливаться так, чтобы значение в списке соответствовало значению в таблице Customer для выбранного клиента. Такой способ выглядит немного грубоватым и определенно содержит больше исходного текста, чем требуется, но он работает.

Совет Без этого вызова код, следующий за вызовом dr.Read, не выполняется. В отличие от набора записей в ADO, в котором при открытии курсор устанавливается на первую запись, в объекте DataReader в ADO.NET курсор устанавливается перед первой записью. Поэтому для получения первой записи (если, конечно, она существует) необходимо вызвать метод Read. Раз или два я потратил около часа, пытаясь получить данные, не выполнив предварительно в объекте DataReader метода Read. Никакой ошибки не было — просто не было данных для отображения. После установки выбранного элемента в раскрывающемся списке State, устанавливается значение свойства Text текстового поля CompanyName равным значению поля CompanyName объекта dr. Последний является объектом типа DataReader, содержащим запись о клиенте, которую я пытаюсь отобразить. Я должен привести это значение в строковый тип, поскольку возвращаемое значение является объектом: CompanyName.Text=(string}dr["CompanyName"];

Примечание Синтаксис используемого здесь приведения хорошо знаком тем, кто программирует на С и C + + , но, наверное, выглядит немного странно для тех, кто программирует на Visual Basic. В Visual Basic .NET эта же операция выполняется с помощью функции СТуре, например CompanyName.Text = CType(dr(«CompanyName»)f String). После определения значения текстового поля CompanyName следует несколько похожих строк кода, каждая из которых устанав-


382

Глава 9

ливает значения текстовых полей на странице EditCustomer.aspx. Определение значения текстового поля ContractEnds немного отличается, так как здесь используется объект типа DateTime, a не строка: ContractEnds.Text= ({DateTim)dr["ContractEnds"]).ToShortDateString(); По этой причине я привожу возвращаемый объект в объект DateTime, а затем вызываю метод ToShortDateString. Если мне не удается прочитать искомую запись (dr.Read возвращает false), я тем не менее вызываю DataBind, чтобы убедиться, что раскрывающийся список State заполнен значениями. При возникновении исключения л приравниваю значение Label! значению свойства Message исключения и делаю кнопку Delete невидимой. Нет смысла удалять то, чего на странице нет. Кнопка Save остается активной, потому что теоретически пользователь может ввести нужную информацию и попытаться сохранить ее. Поскольку весь код, связанный с заполнением текстовых полей находится в методе doDataBind, метод Page_Load довольно прямолинеен:

If ( !(this.IsPostBack) ) {

CustomerID=System.Convert.Tolnt32( (string)Request["CustomerID"]); doDataBind();

I if ( Customer-ID! =0 ) { btnDelete.Visible=true; . else

<

:

btnDelete.Visible=false;

Если до этого отправки формы (postback) не было, то есть пользователь посещает страницу в первый раз, я присваиваю свойству CustomerlD значение, взятое с объекта Request, и вызываю doDataBind для заполнения раскрывающегося списка и текстовых полей. Если значение свойства CustomerlD ненулевое, я де-


Данные и формы ASP.NET

383

лаю видимой кнопку Delete; в противном случае она невидима. Вот собственно и все, что происходит в методе Page_Load. Обработчик события BtnCancel_Ciick умещается в одной строке, которая перенаправляет пользователя на страницу RepeaterTest.aspx — ту самую, которая з обычных условиях приведет на EditCustomer.aspx. Обработчики событий для кнопок Save и Delete несколько сложнее. Далее я подробно расскажу об обработчике 8tnSave_Click. Затем вы самостоятельно исследуете btnDeiete_Click. Сразу после создания подключения я выполняю хранимую процедуру spSaveCustomer с помощью объекта command. Затем добавляю параметр для возвращаемого значения, создавая объект SqlParameter и устанавливая необходимые свойства. Дополнительные параметры добавляются в набор Parameters. Наиболее интересно добавление ContractEnds— параметра поля даты: cmd.Paramete rs.Add("SCont ractEnds", System.DateTime.Parse(Contract Ends.Text));

Поскольку я знаю, что ContractEnds является объектом DateTitne, для разбора даты я прибегаю к методу System.DateTime.Parse. В большинстве случаев я поместил бы этот код в обработчик исключений на случай возникновения исключения. Я почти уверен, что здесь исключения не будет, так как в это место не попасть без проверки правильности введенной даты. После установки всех параметров я создаю подключение и вызываю команду ExecuteNonQuery. После выполнения команды я проверяю возвращаемое значение, которое содержит значение CustomerlD. В случае сохраненной записи это будет то же значение, что я передал в аргументе, а в случае же новой записи это новое значение CustomerlD.

Примечание Как и в ADO, любые команды, которые выполняют запрос, возвращающий записи (например, при вызове возвращающей записи хранимой процедуры с помощью метода ExecuteReader объекта SqlCommand), не позволяют вам получить код завершения или выходные параметры, пока не закрыт объект {например, DataReader), получающий записи.


Глава 9

384

Я сделал так, чтобы хранимая процедура сообщала мне, какая запись возвращена — новая или старая (см. код для spSaveCustomer в листинге 9-8). CREATE PROCEDURE spSaveCustomer @>CustomerID int, @CompanyName nvarchar(50), iAddress nvarchar(50), @City nvarchar(SO), ©State nvarchardO), @PostalCode nvarchar(20), @>ContractEnds datetime, $ContactFirstName nvarchar(SO), @ContactLastName nvarctiar(50), @ContactEMail nvarchar(128), giUserName nvarchar(50), ©Password nvarchar(50) AS SET NOCOUNT ON DECLARE @Ret int

SELECT @Ret=CustomerID FROM Customer WHERE CustomerID=@>CustomerID IF IsNull(@Ret,0)=0 BEGIN INSEHT INTO Customer^ CompanyName , Address , City , State , PostalCode , ContractEnds , ContactFirstName , ContactLastName , ContactEHail , UserNane , [Password] ) VALUES( @CompanyName , ©Address , ©State , ©PostalCode , ©ContractEnds , ^ContactFirstName ©ContactLastName , @ContactEMail , @UserName ,


Данные и формы ASP.NET

385

^Password ) — Будьте аккуратны с триггерами и переменной @@Identlty SET @Ret=№ldentity

END ELSE BEGIN UPDATE Customer SET CompanyName=@CompanyName , Address=@Address ,

City=@City , State=@State , PostalCode=@PostalCode , ContractEnds=@ContractEnds , Contact FirstName=@ContactFirstName , ContactLastName=@Contact LastName , ContactEMail=@ContactEMail , UserName=@UserName , [Password]=@Password , — Устанавливает измененную дату должным образом DateHodified=GetDate() WHERE CustomerID=@Ret

END IF @@Еггог=0 BEGIN Return(@Ret) END ELSE BEGIN Return(O)

END Listing 9-8. Хранимая процедура spSaveCustomer применяется для сохранения строки в таблице Customer Эта хранимая процедура довольно прямолинейна. Вместо того чтобы использовать две разные хранимые процедуры для вставки и обновления, з процессе исполнения spSaveCustomer определяет подходящую команду — INSERTuw UPDATE. Если это новый клиент, я вставляю новую запись и возвращаю значение @@IDENTITY, которое содержит последнее вставленное значение. Совет Если вы работаете с SQL Server 2000, при определенных обстоятельствах лучше использовать IDENT CURRENT или SCOPE IDENTITY. Значение


386

Глава 9

переменной @@IDENTITY может оказаться неверным, если в таблице, в которую вы добавляете запись, есть триггер, вставляющий запись в другую таблицу по ключевому столбцу. Если сохранение прошло успешно, я изменяю значение свойства Text элемента управления Labelt, чтобы отразить этот факт, и изменяю надпись на кнопке Cancel на Close, чтобы показать, что изменения приняты и после выхода со страницы не будут утеряны. Visual Studio .NET дописывает в исходный текст метода InitializeComponent код обработчиков событий для кнопок. Двойной щелчок кнопки в конструкторе добавит код обработчика Click и откроет текст вновь созданного метода з редакторе. В это приложение стоило бы внести несколько незначительных улучшений, если бы оно разрабатывалось для реальной работы. Например, воспользоваться возможностями элемента управления VaUdationSummary, чтобы получать более четкое представление о том, что происходит с каждым из полей. Или создать исполняемый на стороне клиента код для отображения информационного окна с сообщением-подтверждением выполнения операции при щелчке кнопки Delete. Кроме того, дополнительная обработка ошибок или даже ведение журнала ошибок и событий очень кстати з системах, работающих в реальном мире.

Заключение Из всех возможностей ASP.NET доступ к данным — именно та область, которая, по-видимому, предоставляет наибольшее число способов решения поставленной задачи. В примерах, представленных з этой глазе, используется множество объектов в ADO.NET, но ADO.NET намного шире и многообразнее, и я не стал углубляться в детали. Сама технология ADO.NET — это тема отдельной книги, к тому же исчерпывающую информацию о ней зы найдете в MSDN. При выборе объектов я руководствовался собственным вкусом и пристрастиями. Я использовал DataSet, если хотел получить набор данных для отображения в XML, потому что этот объект поддерживает метод CetXml. Обычно объект DataReader обеспечивает все, что мне требуется для разрабатываемого приложе-


Данные и формы ASP.NET

387

ния, кроме того, существует множество примеров использования этого объекта. DataReader работает быстро и эффективно, а так как фактически весь доступ к данным через Web выполняется в одностороннем направлении, я думаю, что применение объектов DataReader станет обычным делом. Впрочем, время покажет. В главе 10 я более подробно расскажу о примерах, представленных в этой главе. Следующим шагом к осуществлению мечты — распространению материалов для партнеров (клиентов) — станет создание XML Web-сервисов. Это еще одна большая группа ASP.NET-приложений. Установка и эксплуатация XML Web-сервисов станет большой частью работы по расширению возможностей Интернета, а также обеспечит совместную работу приложений внутри предприятия.


Глава 10

В ASP.NFT изменились методы создания Web-приложений. Однако, с другой стороны, распространение XML Web-сервисов изменит существующий порядок обмена информацией и предоставления функции в общий доступ. Технология DCOM (Distributed СОМ) — одна из многочисленных попыток предоставить доступ к функциональности программ, работающих на одном компьютере, другим компьютерам сети. DCOM, созданная на базе архитектуры RPC (Remote Procedure Call — вызов удаленных процедур), оказалась намного удобнее RPC, но сохранила при этом часть недостатков RPC. Во-первых, и RPC, и DCOM лучше всего подходят для работы в интрасетях, но не в Интернете. Дело в том, что порты, необходимые RPC и DCOM, очень редко бывают открытыми на корпоративных брандмауэрах. До сумасшедшей популярности, которую переживает сейчас Интернет, ограничения на доступность ресурсов в глобальной сети не мешали пользователям, но в последние годы это стало ощутимым препятствием. Во-вторых, обеспечение поддержки процедур RPC или DCOM в приложении требовало дополнительных, и немалых, усилий. Хотя к DCOM это относится в меньшей степени, чем к RPC, однако в любом случае требовалась серьезные программные и архитектурные изменения. Кроме того, будучи подмножеством технологии COM, DCOM тянет за собой груз тех же недостатков, и хотя работать с СОМ на Microsoft Visual Basic намного легче, чем в других языках (особенно на С+ +), тем не менее трудностей предостаточно. Добавьте к этому необходимость регистрации компонентов в реестре, и вы поймете, что DCOM — отнюдь не идеальное решение.


XML Web-сервисы

389

Очень большое ограничение DCOM — невозможность отделить ее от среды Microsoft Windows, и даже в семействе Windows поддержка с помощью DCOM-решения особенностей каждой из систем этого семейства остается нетривиальной задачей.

Примечание Технология COM Internet Services (CIS) позволяет пользователям DCOM взаимодействовать через HTTP-порты и устанавливать подключения с применением протокола HTTP. CIS решает проблему брандмауэра, однако не отменяет зависимости DCOM от платформы. К счастью, эту проблему удаленного предоставления функций разработчики A5P.NET теперь могут решить no-новому. В этой главе я расскажу, почему XML Web-сервисы обеспечивают стандартный способ предоставления отдельных функций приложения в общий доступ различным процессам и, главное, — машинам. Используя стандартные протоколы и форматы данных, XML Webсервисы позволяют компьютерам обмениваться друг с другом информацией в Интернете и/или интрасетях.

Стандарты XML Web-сервисов Есть очень немного технологий, которые удовлетворяют разработчиков, пишущих для разных платформ и на различных языках. В сущности, единодушно приняты только XML и HTTP. Оба протокола основаны на принципе, который можно коротко сформулировать так: чем проще, тем лучше. XML быстрыми темпами становится универсальным языком данных. Пока не все организации по стандартизации согласились со стандартами XML, и, кроме того, продолжает существовать масса других стандартов (об одном из них — формате HL7, я упоминал в главе 8). Но все новые стандарты в самых разных отраслях несомненно возьмут на вооружение XML, определив для него специализированную грамматику. Протокол HTTP царствует безраздельно. Он есть на любом компьютере, где установлен Web-браузер (последний можно встретить теперь даже на таких устройствах, как сотовые телефоны). На большинстве корпоративных брандмауэров открыты порты


390

Глава 10

как для обычного HTTP, так и для защищенного протокола HTTPS, поддерживающего протокол безопасности Secure Sockets Layer (SSL). Сочетание XML и HTTP представляется логичным. Новый протокол Simpe Object Access Protocol (SOAP), основанный на XML и HTTP, был впервые предложен консорциумом World Wide Web Consortium (W3C) в 1999 году. В SOAP XML пересылается поверх HTTP, что позволяет без проблем отправлять запросы и получить ответы. XML и HTTP —это стандарты, поэтому приложения, которые работают с XML Web-сервисами, можно создавать на любом языке и исполнять на любой удобной платформе. Пом,имо XML и HTTP с SOAP разрешается применять некоторые другие протоколы, в том числе WSDL (Web Services Description Language), UDDI (Universal Description, Discovery, and Integration) и Discovery. Язык WSDL, разработанный Microsoft, IBM и другими, представляет собой XML -схему (XML schema), которая описывает методы и параметры Web-сервиса. XML-схема — это набор тэгов, используемых в XML-документе. Она позволяет точно определить, какие данные содержатся в соответствующем XMLдокументе, а также проверить его целостность. Протокол UDDI — это не зависящий от платформы, открытый каркас описания сервиса, поиска и интегрирования сервисов в Сети. Стандарт UDDI предложили IBM и Microsoft. Более подробную информацию о UDDI вы найдете на сайте http://www.uddi.org. Discovery — это спонсируемый и запатентованный Microsoft протокол поиска XML Web-сервисов. Для обнаружения и запроса доступных функций XML Web-сервисоз в Discovery используется файл с расширением .disco. Этот простой XML-документ содержит ссылки на другие ресурсы, описывающие XML Web-сервисы. Вот пример DISCO-файла: <?xml version="1.О" encoding="utf-8"?> <discovery xmlns:xsi="http://www.w3.org/2001/XHLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://sctiemas.xmlsoap. org/disco/"> <contractRef ref="http://localhost/Chapter10_SimpleService/Simple.asmx?wsdl" docRef="http://localnost/ChapteMO_SimpleService/Simple.asmx" xmlns="http://schemas.xmlsoap.org/disco/scl/" />


XML Web-сервисы

391

<soap address="http://localhost/Chapter10_SimpleService Simple.asmx" xmlns:q1="http://tempuri.org/" binding="q1:SimpleSoap" xmlns="http://sctiemas.xmlsoap. org/disco/soap/" /> </discovery> Здесь говорится, что описание сервиса хранится по адресу http:// localhost/Chapter10_SimpleService/Simple.asmx?wsdl. SOAP существует с 1999 года, но пока лишь немногие разработчики пользуются им для создания SOAP-приложений. Небольшая популярность объясняется главным образом тем, что создавать приложения, поддерживающего SOAP, пока непросто. И это несмотря на то, что создать приложение SOAP легче, чем DCOMприложение. Что касается Windows-разработчиков, то для них, пожалуй, слишком затянулось затишье в методиках разработки перед бурей, поднятой .NET. Так, Microsoft Visual InterDev 6.0 датируется 1997 — 1998 годами, когда о SOAP никто еще не слышал, Сейчас уже выпущено несколько сред разработки с поддержкой SOAP, однако их популярность все еще низка.

Создание простого XML Web-сервиса Для вас вряд ли станет новостью, что в Microsoft Visual Studio .NET имеется мощный инструментарий для разработки XML Web-сервисов. Удобство работы в этой среде просто поражает, в чем вы скоро убедитесь сами. XML Web-сервисы размещаются в папке, которая одновременно является виртуальным каталогом US-сервера. XML Web-сервисы могут иметь те же разрешения безопасности, что и традиционные Web-папки, однако вам придется позаботиться о способе передачи пользовательских реквизитов (credentials) от других программ в XML Web-сервис, чтобы не отображать окно входа в систему. XML Web-сервисы представлены в .NET Framework в виде файлов с расширением .asmx. Это расширение, подобно расширениям .aspx и .ascx, сопоставляется US-серверу, и эти файлы обрабатываются особым образом и не передаются напрямую браузеру, как HTML-файлы, Файл с расширением .asmx может содержать код, но чаще всего в нем находится только ссылка на код, например:


392

Глава 10

<Х@ WebService Language= T vb" Codebehind="Simple.asmx.vb" Class="Chapter10_SimpleService.Simple" %>

Директива WebService аналогична директиве Page в ASPX-файлах. Атрибут Language указывает на язык исходного текста. Атрибут Codebehind используется только о Visual Studio .NET и в аналогичных средствах разработки. В атрибуте Class задается имя класса. Помимо имени класса (в данном случае, ChapterW_$impleService,Simple), в этом атрибуте также разрешается указать имя сборки. Чтобы указать имя моей сборки — Chapter! 0_SimpleService, директиву WebService следует переписать следующим образом: <5Й@ WebService Language="vb"

Codebehind="Simple.asmx,vb"

Class="Chapter10_SimpleService.Simple,Chapter10_SimpleService" K>

Если сборка не задана явно, при первой попытке доступа к XML Web-сервису ASP.NET последовательно просматривает сборки в bin-под каталоге каталога, в котором расположен соответствующий ASMX-файл, в поиске нужной. Такой поиск значительно снижает быстродействие, особенно если в подкаталоге bin много сборок. При работе с Visual Studio .NET код практически никогда не оказывается в ASMX-фаиле, так как по умолчанию среда разработки помещает создаваемый код в файл программной логики, или CodeBehind-файл. В нашем примере код попадет в файл Simple.asmx.vb. Порядок работы в Visual Studio прежний: сначала нужно выбрать тип создаваемого проекта — XML Web-сервис (рис. 10-1).


XML Web-сервисы

393

t Types;

£jl Visual C# Projects 23 Visual C++ Projects - XJ Setup a:id Deployment Projects 03 Other Projects О Visual Studio Solutions

Windows Application

Class Library

Windows 1 Control Libra'

ASP.NETWeb *5Р,ЩТ«еЬ Web Control ! uppicatian = Service i Library A[,i.iji-;tr<f -(«firx/Ml 9Й**»ЙЕМИ •-••£• frл-,.. V-r -,(«,:: i.i.i,-filems:

'

- Г~"^

- | щ Щ

http: //localhost/Chapter 10_S

'ftcaect^tbe «Mere

Рис. 10-1. Диалоговое окно New Project в Visual Studio .NET при создании нового XML Web-сервиса на Visual Basic .NET

Расширение и тестирование XML Web-сервиса При создании нового проекта генерируется базовый XML Webсервис. Visual Studio .NET закрывает строки кода комментариями. Если удалить символы комментария в основном методе, получится простой XML Web-сервис «Hello World». Итоговый CodeBehind-файл, который я немного дополнил, показан в листинге 10-1. Imports System.Web,Services Public Class Simple Inherits System.Web.Services.WebService WRegion " Web Services Designer Generated Code " Public Sub New{) MyBase.New{) 'Этот вызов необходим для Web Services Designer. InitializeComponentC) 'Добавьте собственный код инициализации 'после вызова InitializeComponentQ


394

Глава 10

End Sub 'Необходимо для Web Services Designer Private components As System.ComponentModel.Container 'ПРИМЕЧАНИЕ: Следующая процедура необходима 'для Web Services Designer 'Ее можно модифицировать средствами Web Services Designer. 'Не изменяйте ее средствами редактора кода. <System, Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponentO components = New System.ComponentModel.ContainerO End Sub Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) 'CODEGEN: Эта процедура необходима для Web Services Designer 'He изменяйте ее средствами редактора кода. If disposing Then If Not (components Is Nothing) Then components. DisposeO End If End If MyBase.Dispose(disposing) End Sub «End Region <WebMethod()> Public Function HelloWorld( ByVal Language As String) As String Select Case Language Case "Norwegian" HelloWorld = "God dag Verden" Case "Spanish" HelloWorld = "Hola Hundo" Case "German" HelloWorld = "Hallo Welt" Case Else HelloWorld = "Hello WorldEnd Select End Function End Class Листинг 10-1. Файл Simple.asmx.vb — простой XML Webсервис «Hello World» с поддержкой множества языков


XML Web-сервисы

395

Класс Simple наследует классу System.Web.Services.WebService. B классе WebService много членов, большинство из которых унаследованы от «родителей». Один из наиболее важных — объект Current класса HttpContext', он, в частности, позволяет определить, включена ли трассировка: HttpContext.Си г rent.Trace.IsEnabled; У класса WebService также есть свойство, возвращающее объект Session, который обычно хранит и возвращает по требованию параметры конкретного пользовательского сеанса. При этом следует не забыть включить поддержку состояния сеансов, как описано в таблице 10-1.

Совет Хотя состояние сеансов в ASP.NET можно поддерживать на нескольких Web-серверах кластера, включение этого режима ограничивает масштабируемость приложения. При росте числа Web-серверов любое обращение к состоянию сеанса требует вызова через границы машин. Это очень ресурсоемкая операция, без которой можно обойтись, воспользовавшись другими средствами ASP.NET. Отслеживание состояния сеансов в XML Web-сервисе практически всегда оказывается неудачным решением. V класса WebService есть еще одно интересное свойство —.User. Оно особенно полезно, когда XML Web-сервисы используются внутри организации, в которой установлена служба каталогов Windows 2000 Active Directory и применяется аутентификация Windows. Существует много стандартных способов аутентификации — о них я расскажу в разделе «Параметры безопасности». Свойство Server класса WebService возвращает объект HttpServerUtility, позволяющее получить имя компьютера, получить или установить тайм-аут сценария, а также определить сопоставления путей для различных типов файлов. Методы Execute и Transfer (они знакомы пользователям IIS 5.0) также доступны через свойство Server. В XML Web-сервисе, который показан в листинге 10-1, есть функция HelloWorld:


396

Глава 10

<WebMethod()> _ Public Function HelloWorld( _ ByVal Language As String) As String Select Case Language Case "Norwegian" HelloWorld = "God dag Verden" Case "Spanish" HelloWorld = "Hola Mundo" Case "German"

HelloWorld = "Hallo Welt" Case Else HelloWorld = "Hello World" End Select End Function

Обратите внимание, насколько она проста. С помощью выражения Select/Case я выбираю нужный вариант фразы — это и есть значение, возвращаемое функцией. В этой функции необычен только атрибут <WebMethod()> (на С# выглядит так; [WebMethodOD- Эта «многоязыковая» версия HelloWorld принимает строковый параметр Language (язык) и возвращает фразу «Hello World» на указанном языке (вариантов не очень много: норвежский, испанский и немецкий; в любом другом случае возвращается английский вариант фразы)1. Одна из давнишних проблем тестирования XML Web-сервиса (да вообще любого похожего сервиса) заключается в создании инфраструктуры на стороне клиента, которая и займется выполнением сценария. К счастью, в XML Web-сервисах предусмотрен простой способ самотестирования. Запустите XML Web-сервис (листинг 10-1) —вы увидите страницу, показанную на рис. 10-2.

Благодарю Бенте (Bente) и Йорге Миндюк (forge Mindyk) за перевод на норвежский и испанский языки, а Кэйти Кокс (Kathy Сох) — за перевод на немецкий. Буквальный перевод норвежского варианта звучит примерно как «Добрый день всем», что, по утверждению Бенте, больше соответствует традициям норвежского языка.


XML Web-сервисы

397

Simple

This web senilce Is using http://tempuri.org/ as its default nemespace. Recommendation: Change the default namespace before the XML Web service \s made publii

public class HyBebSaevics ( /V implementation

Рис, 10-2. Страница, полученная в результате запуска XML Web-сервиса Simple в Visual Studio .NET Страница содержит ссылку на единственный открытый метод этого класса: HefioWorld. После щелчка ссылки появится страница, показанная на рис. 10-3. В текстовое поле над кнопкой Invoke можно указать единственный параметр, Language. Если вы забыли, что Language ожидает строку, подсказку найдете, взглянув на SOAP-запрос, показанный ниже на той же странице. Если в текстовое поле ввести «Spanish», на экране появится страница, показанная на рис. 10-4.


398

Глава 10

Simple Hello World

Рис. 10-3. Страница, на которой тестируется метод HelloWorld для ХМ1. Web-сервиса Simple

Рис. 10-4. Результат вызова метода HelloWorld с параметром «Spanish» На рис. 10-4 видно, что в адресной строке URL-адрес содержит только путь ASMX-файла и параметр, который передается точно так же, как на странице ASP.NET с Web-формами. Обратите внимание на предупреждение на рис. 10-2 об используемом XML Web-сервисом пространстве имен http://tempuri.org. В Visual Studio .NET оно используется по умолчанию для всех XML Web-сервисов. Это пространство имен годится для тестирования XML Web-сервисов, однако «в жизни» нужно указывать другое


399

XML Web-сервисы

пространство имен. Чтобы определить для XML Web-сервиса другое пространство имен, прямо перед объявлением класса вставьте примерно такую строку: <WebService(Namespace:="http://ProgrammingA$P.NET/webservices/")> Этот URL-agpec я выдумал, может, он вообще не существует.

Использование свойств атрибута WebMethod Атрибут WebMethod управляет работой XML Web-сервисов. Все его шесть свойств, перечислены в таблице 10-1. Таблица 10-1.

Свойства атрибута WebMethod

Свойство

Описание

BufferResponse

Включает буферизацию откликов от XML Web-cepвиса. Значение по умолчанию равно true, оно подходит для большинства случаев. Если атрибут установлен в false, отклик от XML Web-сервиса отправляется запрашивающему клиенту блоками по 16 кб. Синтаксис определения этого атрибута таков: [Visual Basic. NET] <WebMethod(Buffe ("Response :=False)>

CacheDuration

[WebMetho<J(BufferResponse=false)] Включает кэширование результатов метода XML Web-сервиса, ASP. NET кэширует результаты всех уникальных наборов параметров. Определяет длительность кэширования результатов в секундах. Значение по умолчанию равно О, что означает отсутствие кэширования. Обычно устанавливают другое значение, особенно когда число уникальных наборов параметров невелико, а вероятность изменений невелика. Синтаксис определения этого атрибута: [Visual Basic. NET] <WebMethod (CacheDuration: =60 )>

[CS]

Description

[ WebMethod ( CacheDuration=60)] Описание метода XML Web-сервиса, которое отображается на справочной странице сервиса. Синтаксис определения: [Visual Basic. NET] <WebMethod( Description: ="Text")> [Ctt] [WebMethod(Description="Text")]


400

Таблица 10-1.

Глава 10

(продолжение)

Свойство

Описание

EnableSessian

Включает поддержку состояния сеанса для метода XML Web-сервиса. Когда режим включен, состояние сеанса доступно XML Web-сервису напрямую — из HttpContext.Current.Session или WebService.Session. Синтаксис определения этого атрибута: [Visual Basic.NET] <Web*1ethod(EnableSession:=True)>

tee]

[WebMethod(Ena&leSession=true)]

MessageName

Позволяет XML Web-сервису однозначно идентифицировать перегруженные ме