Инсталляция и тестовый проект

Microsoft .NET Framework — это платформа для создания, развертывания и запуска web-сервисов и приложений. Она предоставляет высокопроизводительную, основанную на стандартах многоязыковую среду, которая позволяет интегрировать существующие приложения с приложениями и сервисами следующего поколения, а также решать задачи развертывания и использования интернет-приложений. .NET Framework состоит из трех основных частей — общеязыковой среды выполнения (common language runtime), иерархического множества унифицированных библиотек классов и компонентной версии ASP, называемой ASP .NET.

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

Давайте обсудим, что такое динамические страницы HTML и чем они отличаются от статических. Статическая страница содержит код на языке гипертекстовой разметки HTML. Когда автор страницы пишет ее, он определяет, как будет выглядеть страница для всех пользователей. Содержание страницы будет всегда одинаковым, независимо от того, кто и когда решит ее просмотреть. Языка HTML вполне достаточно для отображения информации, которая редко изменяется и не зависит от того, кто ее просматривает. Страница HTML — простой ASCII-текст, следовательно, клиент может работать в любой операционной системе.

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

Но что если мы хотим отобразить на странице текущий курс евро или прогноз погоды? Если мы написали страницу HTML вчера, сегодня она уже устареет. Следовательно, мы должны уметь создавать динамические страницы. Динамическое наполнение страницы — это информация, которая отличается от просмотра к просмотру и содержание которой зависит от того, кому она предназначена. Такое наполнение позволяет обеспечить двусторонний обмен информацией — от клиента к серверу и обратно.

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

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

Существуют языки, способные динамически изменять содержимое web-страницы. С одной стороны, это языки скриптов, выполняющиеся непосредственно у клиента. Примеры скриптовых языков — JavaScript и VBScript. Скрипты на этих языках встроены в код HTML, который сервер посылает браузеру. Сценарии, выполняемые на стороне клиента, выделяются тегами <SCRIPT> и </SCRIPT>. Браузер интерпретирует этот код и показывает пользователю результат. Сам код можно просмотреть через View Source браузера. Естественно, эти программы не могут быть большими. Например, если нужно выполнить поиск в базе данных, мы не можем отправить пользователю все ее содержимое. Но скрипты могут проверить правильность запроса, введенного в форму, и тогда не придется перегружать сервер обработкой неправильных запросов. Некоторые программисты создают на JavaScript анимационные эффекты. Одна студентка intuit.ru желала найти скрипт, который бы отправлял SMS-сообщения. Увы, это невозможно. Выполняемых на стороне клиента сценариев недостаточно для создания полноценных динамических страниц. Даже если на странице используется JavaScript и анимированные картинки .GIF, она называется статической.

Динамическая web-страница должна быть создана "на лету" программой, исполняющейся на интернет-сервере. Широко применяется механизм шлюзов CGI (Common Gateway Interface). Вначале пользователь получает статическую страницу с формой. Вам известно, что в теге FORM существует атрибут ACTION. Именно он задает адрес (URL) исполняемого приложения. На сервере находятся исполняемые файлы программ, написанных, например, на C/С++ или Delphi, которые по протоколу HTTP принимают данные из входного потока или из переменных окружения и записывают в стандартный выходной поток готовую страницу.

Пользователю в ответ на запрос посылается HTML-код, который был специально сгенерирован для него. Это может быть, например, результат поиска в поисковой системе. CGI-скрипты могут быть написаны на интерпретируемом языке (Perl) или даже скрипте командной строки. Входной и выходной потоки переназначаются. На вход интернет-сервер принимает данные, введенные пользователем. После обработки полученных данных пользователю возвращается результирующая страница. При исполнении cgi-программа загружается в память сервера, а при завершении — удаляется. Когда 100 клиентов одновременно обращаются к серверу, в памяти создаются 100 процессов, для размещения кода каждого из которых нужна память. Это отрицательно сказывается на масштабируемости. Напомним, что масштабируемость — это возможность плавного роста времени ответа программной системы на запрос с ростом числа одновременно работающих пользователей.

Для решения этой проблемы Microsoft была предложена альтернатива — ISAPI (Internet Server Application Programming Interface) расширения и фильтры. Вместо исполняемых файлов используются DLL-библиотеки. Код DLL находится в памяти все время и для каждого запроса создает не процессы, а нити исполнения. Все нити используют один и тот же программный код. ISAPI-приложение выполняется в процессе IIS-сервера. Это позволяет повысить производительность и масштабируемость.

ISAPI-расширения можно создавать в Visual Studio C++ 6.0, пользуясь мастером.

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

Скриптовые языки, исполняющиеся на стороне сервера, — php и asp. Технология asp была разработана Microsoft в 1990-х годах.

Выполнение кода asp поддерживается ISAPI-расширением сервера. В диалоге конфигурации сервера IIS определяются способы обработки файлов с различными расширениями. Для обработки URL-адреса с расширением в установках сервера определен файл asp.dll. Файлы asp отправляются к нему на обработку. На вход поступает asp, а на выходе имеем поток HTML-кода.

Пример файла asp:

<%@ Language=VBScript %>

<% Option Explicit%>

<HTML>

<HEAD>

<META HTTP-EQUIV="Content-Type" content="text/html">

<TITLE>

Hello ASP World!

<TITLE>

</HEAD>

<BODY>

<P><%

Dim i;

for i=1 to 5

Response.Write("<FONT SIZE=" & i)

Response.Write(">Этот код генерирует ASP!</FONT>")

next i

%>;</P>

</BODY>

</HTML>

Тег <%...%> сигнализирует asp, что в нем находится код, который он должен обрабатывать на сервере. Выполняется скрипт на языке, который указан в директиве Language. Оператор Response.Write записывает текст в выходной поток сервера — таким образом, он становится частью HTML-страницы, отправленной пользователю.

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

Скриптовые языки не поддерживают строгую типизацию. Что это значит? Вы можете не описывать переменную до ее использования и можете присваивать ей значения разных типов. Это удобно, но создает почву для ошибок. Например, у вас есть переменная x1, и вы присваиваете ей значение 1, но вы сделали опечатку и по ошибке написали x2 = 1. Будет создана новая переменная x2, а значение x1 не изменится. В языке со строгой типизацией компилятор заметит, что переменная x2 не описывалась, и выдаст ошибку.

В 2000 году на конференции разработчиков в качестве части новой технологии .NET Microsoft представила ASP+. С выходом .NET Framework 1.0 она стала называться ASP .NET.

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

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

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

Платформа .NET Framework предоставляет приложениям среду выполнения, сама непосредственно взаимодействуя с операционной системой. Выше лежит интерфейс ASP .NET-приложений, на котором в свою очередь базируются web-формы (ASP .NET-страницы) и web-сервисы. Интерфейс .NET Framework позволяет стандартизировать обращение к системным вызовам и предоставляет среду для более быстрой и удобной разработки. CLR обеспечивает единый набор сервисов для всех языков.

ASP .NET использует технологию доступа к данным ADO .NET, которая обеспечивает единый интерфейс для доступа к базам данных SQL Server и файлам XML. Кроме того, усиленная модель безопасности позволяет обеспечивать защиту клиента и сервера от несанкционированного доступа.

В 2004 году появилась версия ASP .NET 2.0 (бета-версия, окончательный выход — конец 2005 — начало 2006 гг.). Как утверждается, эта версия позволяет сократить объем кодирования на 70%. Новые возможности версии 2.0 — использование шаблонов дизайна страниц (Master Page), упрощенная локализация web-приложений, более 50 новых серверных элементов управления. Цели, которые преследовали разработчики новой версии, — повысить скорость разработки сайтов, масштабируемость, легкость поддержки и администрирования сайтов, скорость работы сервера. Появилась панель оснастки MMC (консоль управления Microsoft), предоставляющая графический интерфейс для управления настройками ASP .NET. Изменять настройки проекта теперь можно и через web-интерфейс. ASP .NET 2.0 поддерживает работу на 64-битных процессорах. Сервис персонализации (personalization) предоставляет готовое решение для хранения персональных данных, непосредственно характеризующих пользователя сайта, — так называемого профиля пользователя (Profile).

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

Предыдущие версии Visual Studio для проектов ASP .NET требовали наличия на машине разработчика сервера IIS. Теперь сервер встроен в среду разработки.

ASP .NET 2.0 и Visual Studio 2005 предоставляют инструменты для легкого построения локализируемых сайтов, которые определяют предпочитаемый язык пользователя и посылают ему страницы на его языке.

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

В ASP .NET 2.0 встроена технология автоматического обновления кэширования баз данных. Данные, полученные из базы, хранятся на сервере, и он не обращается к базе для обработки повторного запроса. При изменении базы данных кэш обновляет свое содержимое.

ASP .NET — это технология, а не язык, и позволяет программировать на разных языках — С#, Visual Basic, J#. "В платформе .NET все языки равны, но некоторые — равнее" (Дж. Оруэлл). Вот таким языком и является С#, потому что он был специально создан для этой платформы. Программирование C# позволяет в полной мере использовать концепции, методы и паттерны объектно-ориентированной разработки. Язык Visual Basic 8.0 наделен почти теми же возможностями. Чтобы научиться ASP .NET, вам нужно знать основы HTML, а знание asp не обязательно. Оно может даже помешать, так как придется менять образ мышления. Также для понимания желательно знать CSS и JavaScript.

Первый проект

В начале решите, в какой директории будете создавать страницы. Все файлы, находящиеся в одной директории, считаются единым проектом. Запустите выбранную вами среду разработки. Выберите пункт меню File-New-Website. Появится диалоговое окно. Назначьте в нем имя проекта и выберите язык программирования С#.

По умолчанию проект создается в файловой системе. По желанию его можно создать на HTTP или FTP-сервере. Из файловой системы проект всегда можно скопировать на сервер нажатием одной кнопки в заголовке Solution Explorer.

В проекте будет создана страница default.aspx. Выберите ее, и появится окно редактирования с закладками Design и Source. Не меняя ничего, щелкните на кнопке со стрелкой, чтобы просмотреть страницу в браузере. Появится окно, в котором спрашивается, нужно ли добавить в файл web.config возможность отладки. Нажмите "OK". На панели задач должен появиться значок web-сервера. Откроется браузер, показывающий страницу по адресу http://localhost:номер_порта/Website1/default.aspx. "Localhost" обозначает сервер, работающий на вашем компьютере. Встроенный сервер Cassini сам назначает себе номер порта — для каждого проекта он разный. Сервер IIS обычно работает через порт 80 (или 8080, если тот занят), и для него номер порта указывать не нужно. При этом ваша страница будет скомпилирована.

Пока что страница в браузере пустая.

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

<%@ Page Language="C#" AutoEventWireup="true";  
CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"; 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    </div>
    </form>
</body>
</html>

Разберем эту страницу.

<%@ Page Language="C#" %>. Тег <% всегда предназначается для интерпретации ASP-кода. Директива Page всегда присутствует на странице aspx. Ее атрибут Language — это указание, что в скриптах данной страницы будет использоваться C#, а могли бы VB, C++ или J#. CodeFile — имя файла с отделенным кодом (code-behind). Inherits — класс, определенный в том файле, от которого наследуется класс страницы.

Одновременно будет создан и файл Default.aspx.cs.

Это технология разделения кода, о которой мы уже говорили. Сама форма находится в файле Default.aspx, а в файле Default.aspx.cs находится класс страницы на языке C#. Таким образом, дизайн страницы может быть изменен не затрагивая кода страницы, что позволяет разделить ответственность за внешний вид и работу страницы между дизайнером и программистом.

<form runat="server">

Этот тег дает указание компилятору обрабатывать элементы управления страницы. Обратите внимание на то, что данный тег имеет свойство runat, для которого установлено значение "server" (других значений не бывает). При использовании этого свойства элемент управления обрабатывается компилятором, а не передается браузеру "как есть".

Вставьте в Default.aspx между тегами <form> и </form> тег, задающий элемент управления.

<asp:Label id="Time" runat="server"
 
 Text="Сервер находится в Москве. Московское время: "
/>

Серверный элемент управления Label является средством размещения на странице текста, который может содержать теги HTML. Изменяя значения свойств этого элемента управления в коде, вы можете динамически изменять текст на странице. В asp:Label компилятору сообщается, с каким объектом ведется работа (в рассматриваемом случае — с элементом управления Label).

Далее задаются различные свойства элемента управления. В первую очередь определяется его имя id="time" и атрибут "runat", а также текст.

В файле Default.aspx.cs должен содержаться такой текст:

using System;
......
public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
    
     }
}

Ключевое слово partial появилось в C# 2.0, и позволяет разбить текст определения класса между разными файлами.

System.Web.UI.Page — класс, базовый для всех страниц ASP .NET.

Если вы работаете с WebMatrix, вставьте его самостоятельно между тегами <script> и </script> файла default.aspx.

Вставьте в эту функцию строчку

Time.Text+=DateTime.Now.ToString();

Возможно, вы уже знакомы с классом System.DateTime библиотеки классов FCL — составной части .NET Framework. Здесь мы используем этот класс, как в любой программе на C#. Следовательно, любой класс .NET доступен и в локальных, и в распределенных приложениях.

Полученное значение присваивается свойству Text объекта time. Это элемент управления типа Label (метка), который мы вставили. Время на часах клиента и сервера может не совпадать, если они находятся в разных точках земного шара.

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

Запустите страницу на просмотр кнопкой F5 или нажав на кнопку со стрелкой на панели инструментов. В браузере должна открыться страница, на которой будет написано текущее время. Откройте исходный текст страницы. Никакого кода на С# или элементов управления ASP.NET там не будет:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>Время, вперед</title>
 
</head>
<body>
    <form name="form1" method="post" action="Default.aspx" 
id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
value="/wEPDwUJODExMDE5NzY5D2QWAgIDD2QWAgIBDw8WAh4EVGV4dAUSMDguMDY
uMjAwNiA0OjU2OjQ3ZGRkkEMgqXmKC0v9vwAwh999lefuIOw=" />
</div>
<div>
     <span id="Time">Сервер находится в Москве. Московское время: 
08.06.2006 4:56:47</span>
    </div>
    </form>
</body>
</html>

Обновите страницу. Вы увидите новое значение времени.

Вы можете просмотреть страницу с помощью любого другого браузера.

Если вы находитесь в сети и выход в Интернет осуществляется через прокси-сервер, поставьте галочку на странице Connections (для IE) Bypass proxy server for local addresses.

Заключение

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

ASP .NET — один из компонентов .NET Framework. О платформе .NET Framework можно прочитать в других курсах, например, в курсе С# или лекции 2 курса "Введение в теорию программирования. Функциональный подход", поэтому перечислим кратко основные черты этой технологии:

ADO .NET — набор классов, предназначенных для доступа к базам данных Microsoft SQL Server, к источникам данных OLEDB и к файлам XML.

Разные части вашего проекта могут быть написаны на разных языках, это называется interoperability. Мы попробуем написать проект, где одна из страниц будет на Visual Basic, а другая — на С#.

Компьютерные языки бывают компилируемыми и интерпретируемыми. В процессе компиляции программы, написанной на компилируемом языке, создается выполняемый файл (в Windows — .exe). Он выполняется быстро, но не может исполняться на другой платформе. Машина, на которой она выполняется, должна иметь похожую конфигурацию. Например, если программа использует динамическую библиотеку DLL, то эта библиотека должна быть установлена и на целевой машине. Интерпретируемые программы компилируются в момент выполнения, поэтому они работают медленнее, но не зависят от конкретной машины. В .NET Framework применяется двухэтапная компиляция, то есть первый этап — это компиляция в MSIL, а вторая — компиляция "just-in-time" компилятором во время исполнения. JIT-компилятор оптимизирует код для той машины, на которой он исполняется. В ASP .NET страница компилируется в MSIL при первом обращении клиента к странице. Вместе с ней компилируются классы, которые она использует. Если вы применяете Visual Studio 2005, можно не ожидать первого запроса, а принудительно скомпилировать все страницы вашего проекта. Это позволит выявить синтаксические и другие ошибки.

MSIL — это ассемблер, не зависящий от машины. Он может выполняться на любой машине, где установлена CLR. Проект Mono пытается перенести CLR на другие платформы, позволяя взаимодействовать серверам, работающим на разных платформах.

Вы можете ознакомиться с проектом на сайте http://mono-project.com. На странице http://go-mono.com/archive/xsp-0.10.html находится XSP — сервер ASP .NET, который может служить расширением сервера Apache — основного сервера *nix-систем.

Как работает ASP .NET

Когда мы инсталлируем .NET, в соответствующих директориях C:\WINDOWS\Microsoft.NET\Framework\ помещается также файл aspnet_isapi.dll. Это — ISAPI-расширение, и предназначено оно для получения запросов, адресованных ASP .NET-приложениям (*.aspx *.asmx и т.д.), а также для создания рабочих процессов aspnet_wp.exe, обрабатывающих запросы. Интернет-сервер — IIS или встроенный в WebMatrix и в Visual Studio Cassini — используют это расширение, когда им надо обработать обращение к страницам с расширением aspx.

Этот модуль разбирает (parse) содержимое страниц aspx вместе с файлом отделенного кода и генерирует класс на языке страницы с объектом Page. Страница aspx отличается от обычной HTML-страницы наличием серверных элементов управления, которые описываются специальными тегами. Для понимания работы ASP .NET важно отметить, что каждому тегу элемента управления соответствует свой член класса страницы. Например,

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

преобразуется в

@__ctrl = new global::System.Web.UI.WebControls.Label();

Основная задача объекта Page — посылка HTML-кода в выходной поток. Этот класс компилируется в библиотеку DLL, которая загружается в процесс web-сервера. Последующие запросы к странице также обрабатывает DLL, если исходный код страницы не меняется. Все эти файлы можно найти в директории "Temporary ASP.NET Files" текущей версии .NET. Если мы работаем в среде разработки Visual Studio 2005 или VWD, для каждого проекта создается своя поддиректория.

Типы страниц и папок проекта

В проект можно добавлять новые файлы через диалог New File.

http://www.intuit.ru/department/se/aspdotnet/2/2_1sm.png


увеличить изображение
Рис. 2.1. 

Если снять отметку с флажка "Place code in separate file", Visual Studio создаст один файл, в котором будет и страница, и код для ее обработки (в стиле WebMatrix).

Все файлы помещаются в директорию проекта. И наоборот, все файлы, которые будут помещены в директорию проекта, станут его частью. Для web-сайтов не существует специального файла .csproj, в котором перечислялись бы его составные части, как это было в предыдущих версиях Visual Studio. Структура решения (solution) описывается в текстовом файле .sln. В решение могут входить несколько web-сайтов и библиотек.

В ASP.NET 2.0 имеются специальные поддиректории проекта для разных типов файлов. Например, в папке App_Code хранятся общедоступные классы, текстовые файлы и некоторые другие (DataSet, диаграмма классов). Файлы с расширениями .cs или .vb, попавшие туда, автоматически компилируются, когда запрашивается любая страница проекта. В App_Data содержатся источники данных, используемых в проекте, — базы данных Access и Microsoft SQL, XML-файлы. К этой директории нельзя получить доступ извне, а только из самого приложения. В папке Themes хранятся темы проекта (лекция 13). Применение тем позволяет настроить единообразный внешний вид сайта и централизованно им управлять. В App_GlobalResources находятся ресурсы, например, таблицы строк, которые можно создавать на разных языках. Язык ресурсов выбирается автоматически в зависимости от настроек браузера пользователя. В папке App_WebReferences находятся ссылки на используемые web-сервисы.

Можно создавать собственные поддиректории проекта, например, для хранения картинок.

Проект на двух языках

Проект web-сайта состоит из страниц aspx и классов, которые используются на страницах (и, конечно, разнообразных ресурсов). Файлы с классами, к которым можно обратиться из разных страниц, помещают в специальную папку App_Code. При компиляции они помещаются в одну сборку — библиотеку DLL в формате portable executable. Совершенно неважно, на каком языке написан класс, если это язык .NET.

Готовые скомпилированные сборки сторонних производителей тоже можно использовать в проекте. Их помещают в папку Bin. При этом их необходимо импортировать в проект:

<%@ Import Namespace="MyCustomNamespace" %>

Создайте новый проект. Добавьте в него файл, выбрав тип файла Class и язык Visual Basic. Среда сама предложит поместить его в папку Code. Назовем его CustomClass. У него будет очень простой код. Всего одна функция, которая добавляет слово Hello к имени, переданному в качестве параметра:

Imports Microsoft.VisualBasic

 

Public Class CustomClass

 

    Public Function GetMessage(ByVal name As String) As String

        Return "Hello,  " & name

    End Function

End Class

Добавьте в проект страницу CodeFolder_cs.aspx. Эта страница написана на C#, но она создает класс, написанный на VB .NET:

<%@ page language="C#" %>

 

<script runat="server">

 

  void Button1_Click(object sender, EventArgs e)

  {

    CustomClass c = new CustomClass();

    Label1.Text = c.GetMessage(TextBox1.Text);

  }

</script>

 

<html>

<head>

    <title>ASP.NET Inline Pages</title>

</head>

<body>

    <form id="Form1" runat="server">

      <h1>Welcome to ASP.NET 2.0!</h1>

      <b>Enter Your Name:</b>

      <asp:TextBox ID="TextBox1" Runat="server"/>

      <asp:Button ID="Button1" Text="Click Me"

OnClick="Button1_Click" Runat="server"/>

      <br />

      <br />

      <asp:Label ID="Label1" Text="Hello" Runat="server" />

    </form>

</body>

</html>

На странице определена форма, напоминающая форму Windows-приложения. На ней имеется кнопка, нажатие на которую обрабатывается в функции Button1_Click. В ней создается класс и вызывается его функция GetMessage с параметром, который берется из элемента редактирования. Возвращаемое значение записывается в элемент Label1. В более простом варианте это выглядит так:

Label1.Text = "Hello "+TextBox1.Text;

Класс может быть написан на C#, а использоваться из страницы на Visual Basic:

using System;

public class CustomClass2

{

    public String GetMessage(String input) {

        return "Hello from C# " + input;

    }

}

Код страницы CodeFolder_vb.aspx:

<script runat="server">

 

  Sub Button1_Click(ByVal sender As Object, ByVal e As

System.EventArgs)

    Dim c As New CustomClass2

    Label1.Text = c.GetMessage(TextBox1.Text)

  End Sub

</script>

Однако поместить в директории App_Code можно только на одном языке. Если там будут находиться файлы на разных языках, проект не будет компилироваться. Для того чтобы использовать два языка, необходимо создать поддиректорию, добавить ее в файл web.config и поместить в нее файлы на другом языке.

Регистрация в файле web.config:

<configuration>

  <system.web>

    ...

    <compilation>

      <codeSubDirectories>

        <add directoryName="VBCode"/>

      </codeSubDirectories>

      <codeSubDirectories>

        <add directoryName="CSCode"/>

      </codeSubDirectories>

    </compilation>

    ...

  </system.web>

</configuration>

Директивы

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

Синтаксис объявления директив такой:

<%@ [Directive] [Attribute=Value] %>

Можно объявить несколько директив одновременно:

<%@ [Directive] [Attribute=Value] [Attribute=Value] %>

В ASP .NET 2.0 существует 11 директив.

Директива

Атрибуты

Описание

@Assembly

Name Src

Импортирует на страницу или в элемент управления сборку с заданным именем

@Control

такие же, как у Page

Применяется для задания свойств при создании собственных пользовательских элементов управления

@Implements

Interface

Указывает, что класс данной страницы реализует данный интерфейс

@Import

Namespace

Импортирует пространство имен

@Master

такие же, как у Page

Применяется на страницах шаблона дизайна (Master page). Новая в ASP .NET 2.0

@MasterType

TypeName VirtualPath

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

@OutputCache

Duration Location VaryByCustom VaryByHeader VaryByParam VaryByControl

Управляет кэшированием страницы или элемента управления. Подробнее описана в лекции 15

@Page

см. ниже

Атрибуты, относящиеся к данной странице. Употребляется только в файлах с расширением aspx

@PreviousPageType

TypeName VirtualPath

Страница, с которой были отправлены данные, введенные пользователем. Новая в ASP .NET 2.0. Раньше страницы отправляли пост только самим себе

@Reference

Page Control

Страница или элемент управления, который нужно компилировать вместе с данной

@Register

Assembly Namespace Src TagName TagPrefix

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

Пока что подробно рассмотрим два из них — Page и Import.

Директива Page

Директива Page позволяет установить свойства страницы, которые будут использованы во время компиляции. Эта директива используется чаще остальных, поэтому ее необходимо рассмотреть более подробно.

Наиболее важные атрибуты директивы перечислены в таблице.

AutoEventWireup

Автоматическая обработка событий страницы

Buffer

Управляет буферизацией страницы. По умолчанию буферизуется

ClassName

Позволяет назначать имя класса, сгенерированного данной страницей

CodeFile

Название файла с отделенным кодом для данной страницы

Culture

Устанавливает набор региональных параметров, т.е. язык, формат валюты, даты, чисел

Debug

Если true, на страницу выводится отладочная информация

Trace

Вывод трассировочной информации

EnableViewState

Сохранение состояния страницы. По умолчанию она сохраняется

EnableTheming

Позволяет включить или выключить поддержку тем оформления. По умолчанию включено

Inherits

Класс, от которого наследуется класс данной страницы в технологии отделенного кода

IsAsync

Показывает, обрабатывается ли страница асинхронно

Language

Язык, используемый во внедренном коде

WarningLevel

Наибольший допустимый уровень предупреждений компилятора

CompilerOptions

Опции компилятора

Пространства имен библиотеки классов

Библиотека классов FCL содержит тысячи классов. Для удобства использования они объединены в пространства имен. Чтобы обращаться к классам, объявленным в пространстве имен, без указания полного пути, его нужно импортировать в проект. Если вы хотите работать с файлами формата XML, вам нужно импортировать пространство имен System.XML. В страницах отделенного кода на C#, как всегда, используется директива using.

using System. XML;

На странице aspx — директива Import

<%@ Import Namespace= "System.XML " %>

Для каждого пространства имен требуется отдельная директива Import.

Visual Studio .NET и VWD по умолчанию включают в страницу на C# наиболее часто используемые пространства имен. На страницу aspx импортировать эти пространства имен не требуется.

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

Например, в пространстве имен System.Web.UI находится класс Page, без которого не может существовать ни одна страница ASP .NET, в System.Web — HttpRequest и HttpResponse.

Программа просмотра классов

Как же узнать, какие классы имеются в библиотеке классов .NET? Для этого предназначен Object Browser (Visual Studio 2005) и Class Browser WebMatrix. Чтобы открыть Object Browser в Visual Studio 2005 или VWD Express, выберите пункт меню View —> Object Browser. Если вы используете WebMatrix, то Class Browser находится в той же папке меню Windows, что и WebMatrix — All Programs —> ASP.NET WebMatrix. В формате ASP .NET Class Browser включен в состав Framework SDK.

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

http://www.intuit.ru/department/se/aspdotnet/2/2_2.png— класс

http://www.intuit.ru/department/se/aspdotnet/2/2_3.png— интерфейс

http://www.intuit.ru/department/se/aspdotnet/2/2_4.png— событие

http://www.intuit.ru/department/se/aspdotnet/2/2_5.png— перечисление

http://www.intuit.ru/department/se/aspdotnet/2/2_6.png— метод

http://www.intuit.ru/department/se/aspdotnet/2/2_7.png— свойство

Выберите класс Page. В окошке справа появится список его методов, полей и событий. Если выбрать метод, в третьем окне под вторым появится его описание. Внутри класса находятся еще две папки — классов-прародителей (Base Classes) и классов-потомков. Все их тоже можно просмотреть. Object Browser показывает и классы текущего проекта. Если классы закомментированы тегами генерации документации XML, то эта информация тоже видна, например Summary, Parameters, Values, Returns.

http://www.intuit.ru/department/se/aspdotnet/2/2_8sm.png


увеличить изображение
Рис. 2.8. 

Исследуя свойство IsPostBack, можно узнать, что оно имеет булевское значение и предназначено только для чтения.

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

Существуют разные стандарты HTML и xHTML. Более поздние стандарты предъявляют более строгие требования, например, хHTML 1.1 не разрешает пользоваться <br> и другими простыми тегами без закрывающей косой черты <br/>. В то же время старые стандарты не поддерживают новые теги.

В заголовке HTTP-запроса указывается стандарт документа, Visual Studio 2005 во всех страницах указывает следующий стандарт:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

Этот стандарт требует наличия атрибута xmlns в теге <html> — ссылки на сайт с описанием стандарта.

<html xmlns="http://www.w3.org/1999/xhtml">

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

Свойства страницы

Страница — это основа всего в web-приложении.

Класс System.Web.UI.Page инкапсулирует функциональность, необходимую для создания и обработки страниц ASP .NET.

Каждая страница ASP .NET — это объект класса, который автоматически генерируется ядром ASP .NET. Класс наследуется от ассоциированного со страницей класса, если мы используем отделенный код, или прямо наследуется от System.Web.UI.Page, если код на C# встроен в страницу. Среда также создает конструктор по умолчанию.

Чтобы убедиться в этом, можем создать страницу "PageType.aspx":

<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Тип страницы</title>
</head>
<body>
    <div>
    <% Response.Output.Write("Тип данной страницы 
{0}",this.GetType()); %>
    </div>
     <div>
    <% Response.Output.Write("Базовый тип данной страницы 
{0}",this.GetType().BaseType); %>
    </div>
</body>
</html>

Вот результат:

Тип данной страницы — ASP.pagetype_aspx

Базовый тип данной страницы — System.Web.UI.Page

Такая же страница, созданная по технологии разделения кода,

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

пишет результат:

Тип данной страницы — ASP.pagetype_aspx

Базовый тип данной страницы — PageType

То, что PageType — наследник System.Web.UI.Page, прописано в файле отделенного кода:

public partial class PageType : System.Web.UI.Page

К текущему объекту страницы можно обращаться как к переменной Page. Page — это контейнер элементов управления данной страницы, поэтому содержит в себе коллекцию Controls. Если в теге <head> присутствует атрибут runat = "server", то в Page содержится и поле Header, через которое можно управлять заголовком страницы — например, поменять название страницы в заголовке браузера, назначить файл каскадных таблиц стилей:

<script runat="server">
    protected void Page_Init(object sender, EventArgs e)
    {    
        HtmlLink myHtmlLink = new HtmlLink();
        myHtmlLink.Href = "printable.css";
        myHtmlLink.Attributes.Add("rel", "stylesheet");
        myHtmlLink.Attributes.Add("type", "text/css");
 
        Page.Header.Controls.AddParsedSubObject(myHtmlLink);
        Page.Header.Title = "Новый заголовок";
   }
    protected void Page_Load(object sender, System.EventArgs e)
    {
        Style bodyStyle = new Style();
 
        bodyStyle.ForeColor = System.Drawing.Color.Blue;
        bodyStyle.BackColor = System.Drawing.Color.Beige;
 
        Page.Header.StyleSheet.CreateStyleRule(bodyStyle, null, 
"p");
 
    }
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Hello ASP.NET</title>
</head>
<body>
   <p>Учитесь программировать на ASP.NET</p>
</body>
</html>

В этом примере мы поменяли текст заголовка. Запустите эту страницу. В получившемся HTML-коде внутри тега <title> стоит уже не "Hello ASP.NET", а "Новый заголовок", который был установлен через Page.Header.Title. Был создан стиль для тега <p>, что отразилось таким образом:

<style type="text/css">
   p { color:Blue;background-color:Beige; }
</style>

Внешняя страница CSS была импортирована из файла:

<link href="printable.css" rel="stylesheet" type="text/css" /> 
<style type="text/css">

Если атрибут AutoEventWireup, который присутствует в заголовке страниц, генерируемых VS, установлен, то методы с префиксом Page_ автоматически назначаются обработчиками событий страницы.

У страницы есть два важных свойства — Response и Request. Свойство Response имеет тип HttpResponse. Response страницы можно воспринимать как выходной поток. Весь HTML-код генерируемой страницы в принципе может быть выведен через запись в этот поток. Это был обычный способ работы разработчиков asp. Но в ASP .NET есть более удобные средства вывода данных с помощью серверных элементов управления. Response лучше использовать для записи Cookies, для задания различных параметров заголовка — управлять кэшированием, свойством Expires.

Вот пример из MSDN:

  HttpCookie MyCookie = new HttpCookie("LastVisit");
    DateTime now = DateTime.Now;
 
    MyCookie.Value = now.ToString();
    MyCookie.Expires = now.AddHours(1);
 
    Response.Cookies.Add(MyCookie);

Можно поменять кодовую страницу:

<head runat="server">
<%Response.Charset = "windows-1251"; %>
    <title>Русская кодировка</title>
</head>

Функция Response.Redirect перенаправляет браузер на другую страницу:

Response.Redirect("NavigationTarget.aspx?name=" + System.Web.HttpUtility.UrlEncode(Name.Text);

Здесь формируется командная строка с параметрами QueryString, которые целевая страница может прочитать.

Аналогично, свойство Request — это запрос, переданный на сервер для вывода нужной страницы. Он имеет тип HttpRequest. В нем хранится все о клиенте, включая настройки его браузера, файлы-cookie и данные, введенные им в форму:

NameLabel.Text = Server.HtmlEncode(Request.QueryString["Name"]);

Свойства страницы

Страница — это основа всего в web-приложении.

Класс System.Web.UI.Page инкапсулирует функциональность, необходимую для создания и обработки страниц ASP .NET.

Каждая страница ASP .NET — это объект класса, который автоматически генерируется ядром ASP .NET. Класс наследуется от ассоциированного со страницей класса, если мы используем отделенный код, или прямо наследуется от System.Web.UI.Page, если код на C# встроен в страницу. Среда также создает конструктор по умолчанию.

Чтобы убедиться в этом, можем создать страницу "PageType.aspx":

<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Тип страницы</title>
</head>
<body>
    <div>
    <% Response.Output.Write("Тип данной страницы 
{0}",this.GetType()); %>
    </div>
     <div>
    <% Response.Output.Write("Базовый тип данной страницы 
{0}",this.GetType().BaseType); %>
    </div>
</body>
</html>

Вот результат:

Тип данной страницы — ASP.pagetype_aspx

Базовый тип данной страницы — System.Web.UI.Page

Такая же страница, созданная по технологии разделения кода,

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

пишет результат:

Тип данной страницы — ASP.pagetype_aspx

Базовый тип данной страницы — PageType

То, что PageType — наследник System.Web.UI.Page, прописано в файле отделенного кода:

public partial class PageType : System.Web.UI.Page

К текущему объекту страницы можно обращаться как к переменной Page. Page — это контейнер элементов управления данной страницы, поэтому содержит в себе коллекцию Controls. Если в теге <head> присутствует атрибут runat = "server", то в Page содержится и поле Header, через которое можно управлять заголовком страницы — например, поменять название страницы в заголовке браузера, назначить файл каскадных таблиц стилей:

<script runat="server">
    protected void Page_Init(object sender, EventArgs e)
    {    
        HtmlLink myHtmlLink = new HtmlLink();
        myHtmlLink.Href = "printable.css";
        myHtmlLink.Attributes.Add("rel", "stylesheet");
        myHtmlLink.Attributes.Add("type", "text/css");
 
        Page.Header.Controls.AddParsedSubObject(myHtmlLink);
        Page.Header.Title = "Новый заголовок";
   }
    protected void Page_Load(object sender, System.EventArgs e)
    {
        Style bodyStyle = new Style();
 
        bodyStyle.ForeColor = System.Drawing.Color.Blue;
        bodyStyle.BackColor = System.Drawing.Color.Beige;
 
        Page.Header.StyleSheet.CreateStyleRule(bodyStyle, null, 
"p");
 
    }
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Hello ASP.NET</title>
</head>
<body>
   <p>Учитесь программировать на ASP.NET</p>
</body>
</html>

В этом примере мы поменяли текст заголовка. Запустите эту страницу. В получившемся HTML-коде внутри тега <title> стоит уже не "Hello ASP.NET", а "Новый заголовок", который был установлен через Page.Header.Title. Был создан стиль для тега <p>, что отразилось таким образом:

<style type="text/css">
   p { color:Blue;background-color:Beige; }
</style>

Внешняя страница CSS была импортирована из файла:

<link href="printable.css" rel="stylesheet" type="text/css" /> 
<style type="text/css">

Если атрибут AutoEventWireup, который присутствует в заголовке страниц, генерируемых VS, установлен, то методы с префиксом Page_ автоматически назначаются обработчиками событий страницы.

У страницы есть два важных свойства — Response и Request. Свойство Response имеет тип HttpResponse. Response страницы можно воспринимать как выходной поток. Весь HTML-код генерируемой страницы в принципе может быть выведен через запись в этот поток. Это был обычный способ работы разработчиков asp. Но в ASP .NET есть более удобные средства вывода данных с помощью серверных элементов управления. Response лучше использовать для записи Cookies, для задания различных параметров заголовка — управлять кэшированием, свойством Expires.

Вот пример из MSDN:

  HttpCookie MyCookie = new HttpCookie("LastVisit");
    DateTime now = DateTime.Now;
 
    MyCookie.Value = now.ToString();
    MyCookie.Expires = now.AddHours(1);
 
    Response.Cookies.Add(MyCookie);

Можно поменять кодовую страницу:

<head runat="server">
<%Response.Charset = "windows-1251"; %>
    <title>Русская кодировка</title>
</head>

Функция Response.Redirect перенаправляет браузер на другую страницу:

Response.Redirect("NavigationTarget.aspx?name=" + System.Web.HttpUtility.UrlEncode(Name.Text);

Здесь формируется командная строка с параметрами QueryString, которые целевая страница может прочитать.

Аналогично, свойство Request — это запрос, переданный на сервер для вывода нужной страницы. Он имеет тип HttpRequest. В нем хранится все о клиенте, включая настройки его браузера, файлы-cookie и данные, введенные им в форму:

NameLabel.Text = Server.HtmlEncode(Request.QueryString["Name"]);

События страницы

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

Затем начинается жизненный цикл страницы. Он начинается с этапа PreInit. После получения запроса среда выполнения загружает класс вызываемой страницы, устанавливает свойства класса страницы, выстраивает дерево элементов, заполняет свойства Request, Response и UICulture и вызывает метод IHttpHandler.ProcessRequest. После этого среда выполнения проверяет, каким образом была вызвана эта страница, и если страница вызвана путем передачи данных с другой страницы, о чем будет рассказано далее, то среда выполнения устанавливает свойство PreviousPage.

На этом этапе устанавливается также свойство IsPostback объекта Page, которое позволяет узнать, в первый ли раз загружается форма или она должна формироваться как результат обработки данных, введенных пользователем.

В обработчиках событий страницы можно проверить это свойство:

    if (!Page.IsPostBack)
    {
        // обрабатывать
    }

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

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

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

В методе Render генерируется сам HTML-код выводимой страницы. При этом страница вызывает соответствующие методы дочерних элементов, а те в свою очередь — методы своих дочерних элементов. В методе Render код выводится в Response.OutputStream. Сама страница тоже считается элементом управления — класс Page является наследником класса Control. Если на странице есть блоки отображения, они становятся частью функции отрисовки (rendering).

Наконец, страница выгружается из памяти сервера и происходит событие Unload.

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

<%@ Page Language="C#" Trace ="true" TraceMode="SortByTime" %>

http://www.intuit.ru/department/se/aspdotnet/2/2_9sm.png


увеличить изображение
Рис. 2.9. 

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

Полный список событий страницы, которые можно переопределить в классе страницы:

  1. PreInit
  2. Init
  3. InitComplete
  4. PreLoad
  5. Load
  6. LoadComplete
  7. PreRender
  8. PreRenderComplete
  9. Unload

Для всех событий определены обработчики — виртуальные функции OnInit, OnLoad. Когда AutoEventWireup равно true, в классе автоматически объявляются функции-обработчики событий с префиксом PagePage_Load, Page_Init и так далее. Одно из самых популярных событий — это Page_Load. Генерируя новую страницу, Visual Studio создает обработчик этого события. Здесь можно изменить внешний вид элементов и задать новые. Можно установить AutoEventWireup в false. В таком случае надо писать перегруженные версии виртуальных функций:

protected override void OnInit(EventArgs e)
{
}

Так можно добиться ускорения работы страницы.

Способы внедрения кода ASP .NET в страницу

Есть три способа внедрить код на программном языке в страницу aspx.

Блок <script runat="server"> </script> называется блоком объявления кода. Тег <script> похож на тот, которым вводятся скрипты JavaScript, но с большим отличием — скрипт выполняется на сервере. Поэтому необходимо задавать атрибут runat="server". Атрибут language у него может принимать значения С#, VB, J#. В страницах с отделенным кодом можно писать и на C++. По умолчанию принимается значение языка Visual Basic, поэтому не надо забывать указывать язык, когда пишете на С#. Но нет необходимости это делать, если язык определен в директиве Page. Можно писать на разных языках в одном приложении, но нельзя смешивать разные языки на одной странице.

Внутри блока можно объявлять переменные, константы и функции. На самом деле в C# нет глобальных переменных, так что это будут члены класса страницы. Но они выглядят глобальными, потому что класс не описан программистом, его генерирует ASP .NET. Поэтому будем называть их переменными страницы.

Здесь можно переопределить виртуальные методы класса страницы. В блоке также можно объявлять классы, но они будут внутренними по отношению к классу страницы.

Блоки отображения

Любой код, внедренный с помощью <% и %>, обрабатывается во время события Render как часть страницы. В теле блока <% %> допустимо объявлять переменные (тогда они будут локальными для того метода, в котором данный блок будет реализован), но нельзя объявлять методы или типы.

Такой стиль программирования был характерен для asp. Чаще всего в блоках отображения выводится HTML-код с помощью Response.Write.

<%= someExpr %> является сокращением <% Response.Write (someExpr) %>:

<html>
<head>
</head>
<body>
1 Строка HTML<br />
<% Response.Write ("1 Строка ASP.NET <br />"); %>
2 Строка HTML<br />
<% Response.Write ("2 Строка ASP.NET<br />"); %>
3 Строка HTML<br />
<% ="3 Строка ASP.NET<br />"; %>
</body>
</html>

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

Объект любого класса создается с помощью синтаксиса "тег объекта":

<object id="items" class="System.Collections.ArrayList" 
runat="server"/>

Это эквивалентно описанию в классе страницы поля:

System.Collections.ArrayList items;

Еще один способ применяется для связывания с источниками данных и будет рассмотрен в лекции 7

Заключение

Технология ASP .NET построена на объектно-ориентированном событийно-управляемом подходе к созданию web-страницы. Классы, используемые для генерации web-страниц, являются частью библиотеки .NET Frameworks. Для многих из них существуют аналоги в Windows Forms, что сближает программирование настольных приложений с программированием web-приложений.

Одна из самых важных задач web-разработчика — получение и обработка данных, введенных пользователем. Информация посылается серверу через форму. Форма содержит элементы управления, которые позволяют различными способами вводить информацию.

Формы применяются в большинстве сайтов. Например, если вы пишете письмо в web-интерфейсе, появляется форма с текстовыми полями, соответствующими адресату, теме и тексту письма. Нажатием на кнопку можно добавить прилагаемый файл и окончательно послать письмо кнопкой Send.

Форма HTML содержит теги, такие как текстовое поле, выпадающий список, переключатели (radiobuttons) и флажки (checkbox), кнопки.

Формы ASP .NET отличаются от обычных форм наличием свойства runat="server". Они обрабатываются ASP .NET на сервере. Формы являются одним из полей страницы. На странице находятся элементы управления. Многие из них должны быть расположены внутри формы. ASP.NET позволяет запоминать состояние элементов управления, то есть текст, который был введен в текстовое поле, или выбранный переключатель, передавать его на сервер и обратно на страницу после ее обновления:

<form ID="FormVote" runat="server"></form>

Все формы обрабатываются методом POST. Атрибут Action можно задавать, но не обязательно. По умолчанию это текущая страница.

У элементов управления ASP .NET. тоже имеется свойство runat="server". Второй обязательный атрибут — это его идентификатор, или ID. Он нужен, чтобы обращаться к элементу в программе, то есть это имя члена страницы, по которому мы можем его идентифицировать. Примеры мы уже видели в предыдущих лекциях.

Перечислим группы элементов управления:

Элементы управления HTML являются наследниками класса System.Web.UI.HtmlControls.HtmlControl. Они непосредственно отображаются в виде элементов разметки HTML. Их отображение не зависит от типа браузера. Свойства таких элементов полностью соответствуют атрибутам тегов HTML.

Сравните обычный тег

<input id="Reset1" type="reset" value="reset" />

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

<input id="Reset1" runat="server" type="reset" value="reset" />

Разница заключается только в наличии атрибута ="server". Но он дает колоссальную разницу. Теги сервер отображает как есть, а элементом управления можно манипулировать в коде. Только во втором случае в функции-методе страницы можно написать

Reset1.Value = "АСП";

что равносильно

this.Reset1.Value = "АСП";

Следовательно, Reset1 становится одним из членов класса страницы.

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

Серверные элементы мощнее, потому что они привязаны не к разметке, а к функциональности, которую нужно обеспечить. Многие элементы не имеют аналогов в HTML, например, календарь. Их отрисовка полностью контролируется ASP .NET. Перехватывая события PreRender, Init, Load, можно вмешаться в этот процесс. Объявления серверного элемента управления начинаются с блока <asp:тип> и заканчиваются </asp:тип>

Например:

<asp:Label ID="Label1" runat="server" Text="Hello

World"></asp:Label>

Возможно также закрыть объявление тегом />, если внутри блока нет текста:

<asp:Label ID="Label1" Runat="server" Text="Hello World" />

Свойства этих элементов строго типизированы, в отличие от HTML-элементов.

В этой таблице приведены элементы управления, которые имеют пару среди тегов HTML. Вообще их гораздо больше. Некоторые элементы генерируют не только HTML-код, но и JavaScript. В ASP .NET 2.0 было добавлено множество новых сложных элементов управления, например, MultiView, TreeView, Wizard, GridView. О возможностях одного только GridView можно написать целую статью.

Таблица соответствия некоторых серверных элементов управления тегам HTML

Элемент управления ASP .NET

Соответствующий тег HTML

Назначение

<asp:Label>

<Span>

Отобразить текст

<asp:ListBox>

<Select>

Список выбора

<asp:DropDownList>

<Select>

Выпадающий список

<asp:TextBox>

<Input Type="Text"> <Input Type="Password"> <Textarea>

Строка редактирования Поле редактирования

<asp:HiddenField>

<Input Type="Hidden">

Невидимое поле

<asp:RadioButton>,

<asp:RadioButtonList>

<Input Type="Radio">

Переключатель, список переключателей

<asp:CheckBox>,

<asp:CheckBoxList>

<Input Type="CheckBox">

Флажок, список флажков

<asp:Button>

<Input Type="button"> <Input Type="submit">

Командная кнопка

<asp:Image>

<img>

Изображение

<asp:ImageButton>

<input type="image">

Кнопка-изображение

<asp:Table>

<Table>

Таблица

<asp:Panel>

<Div>

Контейнер

<asp:BulletedList>

<ul>,<ol>

Маркированный список

<asp:HyperLink>

<A Href>

Гиперссылка

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

Все серверные элементы управления находятся в пространстве имен System.Web.UI.Control и наследуются от класса System.Web.UI.Web Controls.WebControl.

Верификация введенных данных.

Их работа тесно связана с внешними источниками данных.

Все существующие классы вы можете просмотреть с помощью Class Browser.

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

Создайте новую форму и нажмите на вкладку Design — переход в режим проектирования. Из выпадающего меню View выберите пункт ToolBox. Элементов так много, что пришлось организовать их в виде дерева. Все нужные в этой лекции элементы управления находятся во вкладке Standard (Web Controls, если у вас WebMatrix). Кнопка F4 открывает окно "Свойство выделенного объекта", а двойной щелчок мышью автоматически создает обработчик наиболее типичного для данного элемента события.

Рассмотрим некоторые простейшие элементы.

Label

Этот элемент управления позволяет выводить отформатированный текст, аналогично обобщенному строчному элементу разметки <SPAN>. Всеми свойствами этого объекта можно управлять из вашей программы ASP .NET.

Большинство методов и свойств унаследовано от WebControl. Главное собственное свойство — это, конечно, его содержание — Text.

Нижеперечисленные свойства управляют внешним видом элемента, присутствуют в классе WebControl и, следовательно, применимы ко всем элементам-наследникам WebControl, а не только к Label.

BackColor

Цвет фона

BorderColor

Цвет границы элемента управления

BorderStyle

Стиль границы — сплошной, пунктир, точечный, двойной и другие

BorderWidth

Ширина границы

Enabled

Активность. Если false, нельзя вводить данные, не получает фокус

Font

Шрифт, состоит из нескольких атрибутов

ForeColor

Цвет, которым отображается текст

Height

Высота элемента

Width

Ширина элемента

Visible

Виден ли элемент управления

TabIndex

Индекс табуляции, в порядке номеров которых в форме перемещается фокус при нажатии на клавишу Tab

ToolTip

Текст окна подсказки

В версии 2.0 появилась возможность задавать для элементов управления "горячие" клавиши, или клавиши быстрого доступа. Свойство AccessKey определяет последовательность нажатых клавиш, которые приводят к установке фокуса на данном элементе. Например, AccessKey="N" значит, что для вызова функциональности надо нажать на Alt+N. Установить фокус в Label невозможно, поэтому устанавливаем свойство AssociatedControlId, которое указывает на другой элемент. Если это TextBox, то фокус устанавливается в него.

Пример описания элемента Label:

<asp:Label id="ShopNews" runat="server" Font-Size =20

ForeColor="red"

 BackColor ="lightgray" BorderWidth=4 BorderStyle=groove Height=50

width=500>

Новости торговой площадки </asp:Label>

Префикс asp: означает, что данный элемент стандартный. Можно создавать собственные контролы со своими префиксами (см. лекцию 14).

Текст между открывающим и закрывающим тегами будет показан на странице. Это содержание его свойства Text.

Так как Text — это такой же атрибут, как и другие, мы можем написать иначе:

<asp:Label id="ShopNews" runat="server" Font-Size =20

ForeColor="red"

 BackColor ="lightgray" BorderWidth=4 BorderStyle=groove Height=50

width=500

 Text= "Новости торговой площадки" />

Вставим это описание в страницу aspx

<html xmlns=""http://www.w3.org/1999/xhtml"">

<head>

  <title>Торговая площадка</title>

</head>

<body>

  <center>

    <h2>

      Демонстрация метки</h2>

    <br />

    <form id="frmDemo" runat="server">

      <asp:Label ID="ShopNews" runat="server" Font-Size="20"

ForeColor="red" BackColor="lightgray"

        BorderWidth="4" BorderStyle="groove" Height="50"

Width="500" Text="Новости торговой площадки" />

    </form>

  </center>

</body>

</html>

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

http://www.intuit.ru/department/se/aspdotnet/3/3_1.png


Рис. 3.1. 

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

<%@ Page Language="C#" %>

<%@ Import Namespace= "System.Drawing" %>

 

<script runat="server">

 

    void Page_Load()

    {

         Label ShopNews = new Label();

         ShopNews.Text = "Новости торговой площадки";

         ShopNews.Font.Size=20;

         ShopNews.ForeColor=Color.Red;

         ShopNews.BackColor=Color.LightGray;

         ShopNews.BorderWidth=4;

         ShopNews.BorderStyle=BorderStyle.Groove;

         ShopNews.Height=50;

         ShopNews.Width=500;

 

         frmDemo.Controls.Add(ShopNews);

    }

 

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

<title>Торговая площадка</title>

</head>

<body>

<center>

<h2>Демонстрация метки</h2><br />

<form id="frmDemo" runat="server">

 

</form>

</center>

</body>

</html>

Обратите внимание на директиву <%@ Import Namespace= "System.Drawing" %>. Директива импорта нужна, чтобы обращаться к перечислению Color названий цветов, определенному в System.Drawing.

Посмотрим, что выдал ASP .NET браузеру. Вот код HTML, относящийся к нашей метке:

<form name="frmDemo" method="post" action="shop.aspx"

id="frmDemo">

<div>

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"

value="/wEPDwULLTEzODAzNzU2NDZkZH95eciStELThSpcgXVWEFYeQxpR" />

</div>

<span id="ShopNews" style="display:inline-block;color:Red;back-

ground-color:LightGrey;border-width:4px;border-style:Groove;font-

size:20pt;height:50px;width:500px;">Новости торговой площадки</span>

</form>

Как видим, ASP .NET превратил метку в элемент разметки <span>, задав ему стили CSS. В форме появился еще один невидимый элемент по имени =__VIEWSTATE, который мы скоро обсудим.

TextBox

Он заменяет элементы разметки <textbox> и <textarea>. Они оба вводят текст, только <textbox> — однострочный, а <textarea> — многострочный. Соответственно, при свойстве textmode, равном MultiLine, получится многострочное поле ввода, а при SingleLine — однострочное. Если textmode равен Password, введенные данные заменяются звездочками, как при <Input Type="Password">. Естественно, это нужно в основном для ввода пароля.

Свойство rows задается при textmode, установленном в MultiLine, и задает количество строк для ввода. Аналогично функционирует свойство columns — количество символов в строке.

Если свойство Wrap установлено, то текст переходит на новую строчку, чтобы полностью помещаться в окне. Эта возможность должна быть хорошо известна по текстовым редакторам:

<script runat="server" language="C#">
  void Page_Load()
  {
    if (Page.IsPostBack)
    {
      if (txtName.Text != "")
        lblName.Text = "Вы ввели имя:" + txtName.Text;
      if (txtAddress.Text != "")
        lblAddress.Text = "Вы ввели адрес:" +
      txtAddress.Text;
      if (txtPassword.Text != "")
        lblPassword.Text = "Вы ввели пароль:" +
          txtPassword.Text + "<br>Спасибо за регистрацию!";
      input.Visible = false;
    }
  }
 
 
</script>
 
<html xmlns=""http://www.w3.org/1999/xhtml"">
<head>
  <title>Регистрация нового пользователя</title>
</head>
<body>
  <form runat="server" id="input">
    <div style="text-align: left">
      <table>
        <tr>
          <td style="width: 100px">
            Введите имя:
          </td>
          <td style="width: 100px">
            <asp:TextBox ID="txtName" runat="server" /></td>
        </tr>
        <tr>
          <td style="width: 100px">
            Введите адрес:</td>
          <td style="width: 100px">
            <asp:TextBox ID="txtAddress" runat="server" 
TextMode="multiline" Rows="5" Wrap="true" /></td>
        </tr>
        <tr>
          <td style="width: 100px">
            Введите пароль:</td>
          <td style="width: 100px">
            <asp:TextBox ID="txtPassword" runat="server" 
TextMode="password" /></td>
        </tr>
      </table>
    </div>
    <br />
    <input type="Submit"><br />
  </form>
  <br />
  <asp:Label ID="lblName" runat="server" /><br />
  <asp:Label ID="lblAddress" runat="server" /><br />
  <asp:Label ID="lblPassword" runat="server" /><br />
</body>
</html>

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

Сохраните эту форму в файле Registration.aspx, мы к ней еще вернемся.

У всех классов, унаследованных от WebControl, в ASP .NET 2.0 появился метод Focus(). Он устанавливает фокус в элемент управления. Чаще всего применяется именно к TextBox. Добавьте txtName. Focus() в событие Page_Load(), и курсор при загрузке страницы уже будет установлен в нужной строке ввода.

RadioButton

Переключатели не ходят в одиночку. Один переключатель на странице не имеет смысла. Нужны хотя бы два. Типичный набор переключателей определяется так:

<asp:RadioButton ID="RadioButton1" Runat="server" Text="Yes" 
GroupName="Set1" />
<asp:RadioButton ID="RadioButton2" Runat="server" Text="No" 
GroupName="Set1"/>

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

У RadioButton есть событие CheckedChanged, которое вызывается, когда пользователь выбирает один из переключателей группы. Чтобы обработчик этого события вызывался, необходимо установить свойство AutoPostBack:

<%@ Page Language="C#" %>
<script runat="server">
 
    protected void option1_CheckedChanged(object sender, 
EventArgs e)
    {
        if(option1.Checked)
            Message.Text = "Вы выбрали " + option1.Text;
        if (option2.Checked)
            Message.Text = "Вы выбрали " + option2.Text;
        if (option3.Checked)
            Message.Text = "Вы выбрали " + option3.Text;
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Шуточное голосование</title>
</head>
<body>
<br /><br />
У кого самые мохнатые лапы в мире?<br /><br />
<form runat="server" id="voting">
 
<asp:RadioButton id="option1" runat="server" Text="медведя" 
OnCheckedChanged="option1_CheckedChanged" AutoPostBack = true 
GroupName="Choice" /><br />
<asp:RadioButton id="option2" runat="server" Text="мохноногого 
тушканчика" OnCheckedChanged="option1_CheckedChanged" 
GroupName="Choice" AutoPostBack = true /> <br />
<asp:RadioButton id="option3" runat="server" Text="хоббитов" 
OnCheckedChanged="option1_CheckedChanged" AutoPostBack = true 
GroupName="Choice"/><br />
<br /><br />
</form>
<asp:Label id="Message" runat="server" />
</body>
</html>

RadioButtonList

Группа переключателей работает так, что выбор одного элемента из группы снимает выбор с других. Этот элемент управления подходит для выбора одного правильного ответа из нескольких:

<asp:RadioButtonList id="radSample" runat="server">
<asp:ListItem id="option1" runat="server" value="Option A" />
<asp:ListItem id="option2" runat="server" value="Option B" />
<asp:ListItem id="option3" runat="server" value="Option C" />
</asp:RadioButtonList>

Свойство radSample.SelectedItem.Value показывает выбранный элемент.

На форуме RSDN каждый участник может открыть голосование по интересующему его вопросу. Вот одно из этих голосований:

<%@ Page Language="C#" %>
<script runat="server">
void Page_Load()
{
    if (Page.IsPostBack)
    {
    if( radVoting.SelectedItem==null)
        Message.Text = "Надо выбрать вариант";
        else
        {
        Message.Text = "Спасибо за участие. Вы выбрали: " + 
radVoting.SelectedItem.Value;
        voting.Visible=false;
        }
    }
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title> Что вы думаете о сайте intuit.ru? </title>
</head>
<body>
<br /><br /> <asp:label runat="server" ID="Question" Font-
Bold="True" Font-Size="Large" ForeColor="#0000C0">Что вы думаете 
об онлайн-университете intuit.ru? </asp:label>
<br /><br />
<form runat="server" id="voting">
<asp:RadioButtonList id="radVoting" runat="server"  >
<asp:ListItem value="Хороший сайт, учился там" />
<asp:ListItem value="Есть интересные курсы, причем в открытом 
доступе" />
<asp:ListItem value="Не бывал(а), пойду посмотрю" />
<asp:ListItem value="Посмотрел(а), записался на курсы" />
</asp:RadioButtonList>
<input type="Submit" value="Проголосовать!" >
<br /><br />
</form>
<asp:Label id="Message" runat="server" />
</body>
</html>

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

CheckBoxList

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

    <asp:Label ID="Label1" runat="server" Text="Как 
реализуется связывание с данными в ваших ASP.NET 2.0 
приложениях?"></asp:Label>
    <asp:CheckBoxList ID="CheckBoxList1" runat="server">
        <asp:ListItem>На каждой страничке создаётся 
SqlDataSource, с ним идёт декларативное связывание.</asp:ListItem>
        <asp:ListItem>В проекте есть DataSet, на страничках 
ObjectDataSourcе'ы, и с ними декларативное 
связывание.</asp:ListItem>
        <asp:ListItem>Недекларативное связывание, запрос 
прописан на каждой страничке.</asp:ListItem>
        <asp:ListItem>Недекларативное связывание с 
использованием одного большого DataSet'а</asp:ListItem>
    </asp:CheckBoxList>

Если в CheckBoxList множество вариантов, то можно их расположить в несколько столбцов. При этом можно двигаться сверху вниз — справа налево, или наоборот. Это зависит от RepeatDirectionHorizontal или Vertical. Текст может быть расположен справа или слева от флажка.

DropDownList

Аналог этого элемента в HTML — выпадающие меню. Они задаются с помощью тегов <SELECT> и </SELECT>, между которыми находятся один или несколько тегов <OPTION>.:

<BR>Каталог
<SELECT NAME="Выбор">
<OPTION>Компьютеры
<OPTION>Принтеры
<OPTION VALUE="Комплектующие"> Комплектующие
<OPTION SELECTED>Мониторы
<OPTION>Компакт-диски
</SELECT> 

DropDownList выдаст тот же самый код, если будет определен так:

<asp:DropDownList id="Выбор" runat="server">
<asp:ListItem> Компьютеры </asp:ListItem >
<asp:ListItem >Принтеры</asp:ListItem >
<asp:ListItem > Комплектующие </asp:ListItem >
<asp:ListItem Selected="true"> Мониторы </asp:ListItem >
<asp:ListItem > Компакт-диски</asp:ListItem >
</asp:DropDownList >

Итак, если мы хотим перейти с написания страниц HTML к написанию кода ASP:

<asp:DropDownList> пишется вместо <SELECT>
<asp:ListItem > вместо <OPTION>

Вместо атрибута NAME пишем атрибут id.

Вместо атрибута SELECTED пишем атрибут Selected и присваиваем ему true.

Атрибут VALUE можно задать и в HTML. Если его нет, ASP .NET генерирует его из текста ListItem:

<select name="Выбор" id="Выбор">
   <option value=" Компьютеры "> Компьютеры </option>
   <option value="Принтеры">Принтеры</option>
   <option value=" Комплектующие "> Комплектующие </option>
   <option selected="selected" value=" Мониторы "> Мониторы </option>
   <option value=" Компакт-диски"> Компакт-диски</option>
</select>

Отличие серверных элементов управления в том, что их можно программировать на сервере и получать информацию от них тоже на сервере.

Попробуем написать такую страницу:

<%@ Page Language="C#" %>
 
<script runat="server" language="C#">
  void Page_Load()
  {
    if (Page.IsPostBack)
      lblMessage.Text = "Вы выбрали " + Category.SelectedItem.Value;
  }
</script>
 
<html xmlns=""http://www.w3.org/1999/xhtml"">
<head>
  <title>Выбор категории товаров</title>
</head>
<body>
  <br />
  <form id="Form1" runat="server">
    Выберите категорию товаров<br />
    <asp:DropDownList ID="Category" runat="server">
      <asp:ListItem> Компьютеры </asp:ListItem>
      <asp:ListItem>Принтеры</asp:ListItem>
      <asp:ListItem> Комплектующие </asp:ListItem>
      <asp:ListItem Selected="true"> Мониторы </asp:ListItem>
      <asp:ListItem> Компакт-диски</asp:ListItem>
    </asp:DropDownList>
    <input type="Submit">
    <br />
    <asp:Label ID="lblMessage" runat="server" />
  </form>
</body>
</html>

Запустите ее на выполнение, выберите "Компакт-диски" и нажмите на кнопку. На форме появится надпись "Вы выбрали Компакт-диски".

Вот код этой страницы в браузере:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Выбор категории товаров</title>
</head>
<body>
<br/>
<form name="ctl00" method="post" action="DropDownList.aspx" 
id="ctl00">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
value="/wEPDwUKLTc3Mjg2Njg5MQ9kFgICAQ9kFgICAw8PFgIeBFRleHQFLtCS0Ys
g0LLRi9Cx0YDQsNC70LggINCa0L7QvNC/0LDQutGCLdC00LjRgdC60LhkZGRURXB3F
72jDHrywdZ12h2Cw2f41A==" />
</div>
 
Выберите категорию товаров<br />
<select name="Category" id="Category">
   <option value=" Компьютеры "> Компьютеры </option>
   <option value="Принтеры">Принтеры</option>
   <option value=" Комплектующие "> Комплектующие </option>
   <option value=" Мониторы "> Мониторы </option>
   <option selected="selected" value=" Компакт-диски"> Компакт-
диски</option>
 
</select>
<input type="Submit">
<br />
<span id="lblMessage">Вы выбрали Компакт-диски</span>
 
<div>
 
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDA-
TION" value="/wEWBgKTtKPTDgLc5pLAAgLxxYKuBALL14rfCALaoI/9AgLP6q/dC9yI/KG
10xK67UImbEUJkicDdslR" />
</div></form>
</body>
</html>

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

Посмотрим, как ASP .NET интерпретировал заголовок формы:

<form name="ctl00" method="post" action="DropDownList.aspx" 
id="ctl00">

Атрибуты name и id он сгенерировал самостоятельно. Их значение одинаково, разные браузеры позволяют обращаться к элементам формы по-разному: IE — через id, а Netscape Navigator — через name. Атрибуты method и action тоже не были указаны нами, он сгенерировал их по умолчанию как post и текущая страница.

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

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

Свойство Items элемента управления DropDownList имеет несколько методов для добавления и удаления строк. Используя методы Add и Insert, можно добавить элемент или вставить его в указанную позицию в DropDownList; AddRange позволяет добавить массив элементов в DropDownList; метод Clear удаляет все элементы; методы Remove и RemoveAt удаляют указанный элемент или элемент, находящийся в указанной позиции соответственно. Например, так можно программно создать DropDownList в функции Page_Load:

    Category = new DropDownList();
    Category.Items.Add("Компьютеры");
    Category.Items.Add("Принтеры");
    Category.Items.Add("Комплектующие");
 
    ListItem selItem = new ListItem("Мониторы", "мониторы");
    Category.Items.Add(selItem);
    Category.Items.Add(new ListItem("Компакт-диски"));
    Category.SelectedIndex = 3;

Чтобы очистить выбор в элементе DropDownList, установите SelectedIndex в - 1.

Если установить у ListItem свойство Enable в false, то он будет не виден в списке, однако с ним можно работать из программы. Событие SelectedIndexChanged запускается, когда пользователь выбирает другой элемент.

ListBox

Если у тега <select> указать атрибут SIZE, больший 1 (значение по умолчанию), то получим простой невыпадающий список. Ему соответствует <asp:ListBox>.

Элемент управления позволяет выбрать несколько пунктов списка одновременно. Для этого надо установить его свойство SelectionMode:

SelectionMode="multiple"

Свойство Rows устанавливает количество элементов, которые видны в листе. Если элементов больше, то появляется полоса прокрутки.

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

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

<%@ Page Language="C#" %>
 
<script runat="server">
 
  void Page_Load()
  {
    string msgCitiesList = "";
    if (Page.IsPostBack)
      foreach (ListItem it in cities.Items)
        if (it.Selected)
        {
          msgCitiesList = msgCitiesList + it.Text + "<br />";
        }
 
    if (msgCitiesList != "")
    {
      Message.Text = "Вы выбрали следующие города: <br />" + 
msgCitiesList;
    }
    else
    {
      Message.Text = "";
    }
  }
 
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Пример ListBox</title>
</head>
<body>
  Какие города вы хотите включить в свой маршрут?<br />
  <form id="Form1" runat="server">
    <asp:ListBox ID="cities" runat="server" SelectionMode="multiple">
      <asp:ListItem>Лондон</asp:ListItem>
      <asp:ListItem>Мадрид</asp:ListItem>
      <asp:ListItem>Париж</asp:ListItem>
      <asp:ListItem>Рига</asp:ListItem>
    </asp:ListBox><br />
    <input type="Submit">
    <p>
      <asp:Label ID="Message" runat="server" /><br />
  </form>
</body>
</html>

Событие SelectedIndexChanged имеется и тут, как и у всех классов-наследников от абстрактного класса ListControl.

Panel

Часто бывает нужно вставить элемент управления в точно определенное место страницы. Например, объединим два предыдущих примера:

<%@ Page Language="C#" %>

<%@ import Namespace="System.Drawing" %>

<script runat="server">

 

    void Page_Load()

    {

 

         Label ShopNews = new Label();

         ShopNews.Text = "Новости торговой площадки";

         ShopNews.Font.Size=20;

         ShopNews.ForeColor=Color.Red;

         ShopNews.BackColor=Color.LightGray;

         ShopNews.BorderWidth=4;

         ShopNews.BorderStyle=BorderStyle.Groove;

         ShopNews.Height=50;

         ShopNews.Width=500;

 

         frmDemo.Controls.Add(ShopNews);

 

    if (Page.IsPostBack)

    lblMessage.Text = "Вы выбрали " + Category.SelectedItem.Value;

    }

 

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

<title>Выбор категории товаров</title>

</head>

<body>

<br/>

<form id="frmDemo" runat="server">

Выберите категорию товаров<br />

<asp:DropDownList id="Category" runat="server">

<asp:ListItem> Компьютеры </asp:ListItem >

<asp:ListItem >Принтеры</asp:ListItem >

<asp:ListItem > Комплектующие </asp:ListItem >

<asp:ListItem Selected="true"> Мониторы </asp:ListItem >

<asp:ListItem > Компакт-диски</asp:ListItem >

</asp:DropDownList >

<br/>

<input type="Submit" value="Выбрать">

<br/><br/>

<asp:Label id="lblMessage" runat="server"/>

</form>

</body>

</html>

http://www.intuit.ru/department/se/aspdotnet/3/3_2.png


Рис. 3.2. 

Эта страница работает не так, как нам хотелось. Текст оказался после выпадающего списка, хотя логичней было бы, чтобы текст шел в начале. В HTML для этого используют элемент разметки <DIV> — стандартный контейнер. Его аналог в ASP .NET:

<asp:Panel>.

Чтобы заставить метку отображаться перед списком, необходимо поместить перед DropDownList объект Panel:

<asp:Panel ID="Panel1" runat="server"></asp:Panel><br />

после чего вызывать метод Controls.Add(...) от этого объекта:

Panel1.Controls.Add(ShopNews);

http://www.intuit.ru/department/se/aspdotnet/3/3_3.png


Рис. 3.3. 

Свойство HorizontalAlign элемента Panel полезно, если нужно установить выравнивание содержащихся в нем элементов управления. Поменяем код в предыдущем примере:

<asp:Panel ID="Panel1" runat="server" HorizontalAlign="Center"

width=500 />

Текст теперь размещается в центре метки.

Булевское свойство Wrap элемента Panel заставляет переносить текст на новую строку, если оно установлено, или расширять раздел, когда текст не помещается в одну строку, если оно не установлено.

Если в программе установить свойство Visible панели в False, можно сделать невидимыми все элементы, которые в нем находятся. Стили, установленные в панели, наследуются всеми вложенными элементами.

Новая возможность в ASP .NET 2.0 — задавать для Panel полосы прокрутки, как бы имитируя встроенное в страницу окно. Это делается с помощью свойства ScrollBars. Он может принимать следующие значения: None, Both, Vertical, Horizontal, Auto. Если вы установите его в Auto, полосы прокрутки появятся, когда содержимое панели не умещается в ее видимые размеры:

        <asp:Panel ID="Panel1" runat="server" Height="140px"

Width="494px" ScrollBars="Auto" HorizontalAlign="Left">

            </asp:Panel>

    protected void Page_Load(object sender, EventArgs e)

    {

        for(int i=0;i<100;i++)

        {

            Literal l=new Literal();

            l.Text = "Мой дядя самых честных правил,<br>Когда не

в шутку занемог<br><br>";

            this.Panel1.Controls.Add(l);

        }

    }

Реализуется такая возможность с помощью атрибута css overflow:

   <div id="Panel1" style="height:140px;width:494px;

overflow:auto;text-align:left;">

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

Ставить свойство ScrollBars в Vertical или Horizontal я вам не рекомендую. Генерируется overflow-x, а это не работает в браузере Opera 9.0. overflow не поддерживается Opera 6.

Вертикальную полосу прокрутки можно установить и слева. Для этого поменяйте свойство Direction в RightToLeft.

Для Panel можно задать фоновую картинку с помощью свойства BackImageUrl.

Заключение

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

Вначале — маленькая "хитрость". Хороший web-разработчик должен знать, как выглядят его страницы в разных браузерах. По умолчанию обычно они открываются в Internet Explorer. Щелкните правой кнопкой мыши на файле и выберите Browse with. Там можно сменить браузер по умолчанию или выбрать любой из списка для данного просмотра.

Button

Button — это командная кнопка, нажатие на которую часто приводит к отправке данных на сервер. Можно создавать кнопки двух типов: для передачи данных формы (submit button) или командные кнопки для выполнения различных функций, связанных с данной кнопкой. Если на форме есть несколько кнопок, свойство CommandName позволяет узнать, какая именно кнопка была нажата.

ASP .NET поддерживает 3 вида событий.

Например, чтобы обработать щелчок на кнопке, мы переопределяем событие Click.

    protected void Button1_Click(object sender, EventArgs e)

    {

 

    }

События можно определить через вкладку событий в окне свойств. Второй аргумент всех обработчиков событий имеет тип EventArgs или какой-либо унаследованный от него.

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

<%@ Page Language="C#"%>

<script runat="server">

 

static int num=0;

static TextBox[] tb=new TextBox[10];

void AddExperience(Object sender, EventArgs e)

{

    if (num < 10)

// Чтобы не возникало ошибки обращения к несуществующему элементу массива

    {

        TextBox newBox = new TextBox();

        newBox.ID = "box" + num;

        tb[num] = newBox;

        num++;

     }

    for (int i=0; i<10; i++)

// Добавление на форму контролов из

массива.

    {

        if (tb[i] != null)

        {

        places.Controls.Add(tb[i]);

        Label lb=new Label();

        lb.Text="<br><br>";

        places.Controls.Add(lb);

        }

        else break;

    }

}

 

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

</head>

<body>

    <form runat="server" id="Experience">

    <asp:Label ID="Label1" runat="server" text="Введите Ваше

        последнее место работы" />

    <asp:Panel id="places" runat="server">

        <asp:TextBox id="first" runat="server" />

        <br />

        <br />

        </asp:Panel>

        <asp:Button id="Add" Text="Еще" OnCommand="AddExperience"

CommandName="Add" runat="server" />

    </form>

</body>

</html>

Здесь мы имеем массив из 10 элементов типа TextBox. Новый элемент создается в момент нажатия на кнопку "Еще". Можно добавить до 10 новых элементов. Как и раньше, они размещаются в контейнере, это нужно, чтобы они выводились до кнопки.

При помощи свойства OnClientClick можно задать клиентский сценарий на JavaScript. Его значением может быть встроенная функция языка JavaScript, или функция, описанная в теле страницы. Клиентский код выполняется до серверного кода, заданного в свойстве OnClick.

Image

Элемент управления asp:image соответствует тегу img языка HTML. Его можно использовать для динамического добавления на страницу новых изображений. Вернемся к нашему туристическому агентству. Мы решили, что, когда клиент выбирает города, на страницу автоматически должна выводиться карта соответствующего города. Оставляем это в качестве упражнения. Карты городов можно найти через поисковую систему Яндекс.

<asp:Image> имеет свойства AlternateText, ImageUrl, ImageAlign

AlternateText

Соответствует атрибуту ALT тега IMG. Отображается, если показ картинок отключен или картинки невозможно найти

ImageUrl

Соответствует атрибуту SRC тега IMG

ImageAlign

Соответствует атрибуту ALIGN тега IMG

Как всегда, свойства можно менять из программы. Например, меняя значение ImageUrl, можно организовать просмотр множества картинок в виде слайд-шоу. Создайте директорию images и поместите в нее несколько картинок image1, image2 и так далее.

Напишем новую страницу:

<%@ Page Language="C#" %>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

 

<script language="C#" runat="server">

public static int count=1;

void NextImage(Object sender, EventArgs e)

{

count++;

if(count==10) count=1; //циклический просмотр

Image1.ImageUrl ="images/image"+count+".jpg";

}

</script>

</head>

 

<body>

   <form runat="server">

      <h3>Image Example</h3>

      <asp:Image id="Image1" runat="server"

           ImageAlign="top"

           AlternateText="Картинки нет"

           height="300"

           ImageUrl="images/image1.jpg"/>

      <hr>

<br><br>

      <asp:Button id="Next"

           Text="Next"

           OnClick="NextImage"

           runat="server"/>

<br><br>

   </form>

</body>

</html>

Вначале — маленькая "хитрость". Хороший web-разработчик должен знать, как выглядят его страницы в разных браузерах. По умолчанию обычно они открываются в Internet Explorer. Щелкните правой кнопкой мыши на файле и выберите Browse with. Там можно сменить браузер по умолчанию или выбрать любой из списка для данного просмотра.

Button

Button — это командная кнопка, нажатие на которую часто приводит к отправке данных на сервер. Можно создавать кнопки двух типов: для передачи данных формы (submit button) или командные кнопки для выполнения различных функций, связанных с данной кнопкой. Если на форме есть несколько кнопок, свойство CommandName позволяет узнать, какая именно кнопка была нажата.

ASP .NET поддерживает 3 вида событий.

Например, чтобы обработать щелчок на кнопке, мы переопределяем событие Click.

    protected void Button1_Click(object sender, EventArgs e)

    {

 

    }

События можно определить через вкладку событий в окне свойств. Второй аргумент всех обработчиков событий имеет тип EventArgs или какой-либо унаследованный от него.

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

<%@ Page Language="C#"%>

<script runat="server">

 

static int num=0;

static TextBox[] tb=new TextBox[10];

void AddExperience(Object sender, EventArgs e)

{

    if (num < 10)

// Чтобы не возникало ошибки обращения к несуществующему элементу массива

    {

        TextBox newBox = new TextBox();

        newBox.ID = "box" + num;

        tb[num] = newBox;

        num++;

     }

    for (int i=0; i<10; i++)

// Добавление на форму контролов из

массива.

    {

        if (tb[i] != null)

        {

        places.Controls.Add(tb[i]);

        Label lb=new Label();

        lb.Text="<br><br>";

        places.Controls.Add(lb);

        }

        else break;

    }

}

 

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

</head>

<body>

    <form runat="server" id="Experience">

    <asp:Label ID="Label1" runat="server" text="Введите Ваше

        последнее место работы" />

    <asp:Panel id="places" runat="server">

        <asp:TextBox id="first" runat="server" />

        <br />

        <br />

        </asp:Panel>

        <asp:Button id="Add" Text="Еще" OnCommand="AddExperience"

CommandName="Add" runat="server" />

    </form>

</body>

</html>

Здесь мы имеем массив из 10 элементов типа TextBox. Новый элемент создается в момент нажатия на кнопку "Еще". Можно добавить до 10 новых элементов. Как и раньше, они размещаются в контейнере, это нужно, чтобы они выводились до кнопки.

При помощи свойства OnClientClick можно задать клиентский сценарий на JavaScript. Его значением может быть встроенная функция языка JavaScript, или функция, описанная в теле страницы. Клиентский код выполняется до серверного кода, заданного в свойстве OnClick.

Image

Элемент управления asp:image соответствует тегу img языка HTML. Его можно использовать для динамического добавления на страницу новых изображений. Вернемся к нашему туристическому агентству. Мы решили, что, когда клиент выбирает города, на страницу автоматически должна выводиться карта соответствующего города. Оставляем это в качестве упражнения. Карты городов можно найти через поисковую систему Яндекс.

<asp:Image> имеет свойства AlternateText, ImageUrl, ImageAlign

AlternateText

Соответствует атрибуту ALT тега IMG. Отображается, если показ картинок отключен или картинки невозможно найти

ImageUrl

Соответствует атрибуту SRC тега IMG

ImageAlign

Соответствует атрибуту ALIGN тега IMG

Как всегда, свойства можно менять из программы. Например, меняя значение ImageUrl, можно организовать просмотр множества картинок в виде слайд-шоу. Создайте директорию images и поместите в нее несколько картинок image1, image2 и так далее.

Напишем новую страницу:

<%@ Page Language="C#" %>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

 

<script language="C#" runat="server">

public static int count=1;

void NextImage(Object sender, EventArgs e)

{

count++;

if(count==10) count=1; //циклический просмотр

Image1.ImageUrl ="images/image"+count+".jpg";

}

</script>

</head>

 

<body>

   <form runat="server">

      <h3>Image Example</h3>

      <asp:Image id="Image1" runat="server"

           ImageAlign="top"

           AlternateText="Картинки нет"

           height="300"

           ImageUrl="images/image1.jpg"/>

      <hr>

<br><br>

      <asp:Button id="Next"

           Text="Next"

           OnClick="NextImage"

           runat="server"/>

<br><br>

   </form>

</body>

</html>

ImageButton

Элемент управления ImageButton представляет собой комбинацию элементов Image и Button. Его можно использовать для создания изображений, чувствительных к клику мышки. Щелчок является событием, при наступлении которого выполняется некоторый код:

<asp:ImageButton

id="imgButton"

OnCommand ="SubmitPartl"

runat="server"

/>

ImageButton позволяет достичь эффекта, аналогичного карте изображения. Событие Click позволяет узнать координаты щелчка мыши и реагировать соответственно региону, в котором была нажата мышь. Обработчик события должен принимать аргумент типа ImageClickEventArgs — наследника System.EventArgs. У него есть дополнительные поля X и Y — координаты клика мышки:

protected void ImageButton1_Click(object sender,

System.Web.UI.WebControls.ImageClickEventArgs e)

{

// обработка события

}

Вернемся опять в туристическое агентство. Директор вызвал вас (программиста) и попросил воплотить следующее: на странице с изображением карты Каира необходимо сделать так, чтобы когда пользователь нажимал мышкой в любое место, открывалась карта района, на который он нажал. Всего имеется 9 районов. Все картинки имеют размер 300 на 300 пикселов. Районы одинаковые, расположены в таблице 3 на 3.

Вы пишете:

<%@ Page Language="C#" %>

<script runat="server">

 

    void Magnify(Object sender, ImageClickEventArgs e)

    {

    int x=e.X;

    int y=e.Y;

      int row=y/100;   // строка, на которую кликнули

      int col=x/100;   // столбец, на который кликнули

 

      int count=row*3+col+1;    //номер картинки

    plan.ImageUrl ="Cairo/map-"+count+".jpg";

    plan.Enabled = false;       // это нужно, чтобы не открывался

другой район.

    instruction.Text="Нажмите Back, чтобы увидеть весь город";

    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

</head>

<body>

    <form runat="server">

        <h3>Карта Каира

        </h3>

        <br />

        <br />

        <asp:Label id="instruction" runat="server">Нажмите мышью

на любой район, чтобы увеличить картинку.</asp:Label>

        <br />

        <br />

        <asp:ImageButton id="plan" onclick="Magnify" runat="server" width="300" height="300"

ImageUrl="Cairo/map.gif"></asp:ImageButton>

        <br />

        <br />

    </form>

</body>

</html>

А более простое использование — создание красивых нестандартных кнопок. Свойство CommandName используется так же, как и в случае с обычной кнопкой:

    <form id="form1" runat="server">

    <div>

        <asp:ImageButton ID="ImageButton1" runat="server"

CommandName="create" ImageUrl="~/Images/1button-create.gif"

OnCommand="ImageButton_Click" OnClientClick='alert("clicked")'

ToolTip="Create very nice account" />

        <asp:ImageButton ID="ImageButton2" runat="server"

CommandName="add" ImageUrl="~/Images/1button-add.gif"

OnCommand="ImageButton_Click" />

        <asp:ImageButton ID="ImageButton3" runat="server"

ImageUrl="~/Images/1button-cancel.gif" CommandName="cancel"

OnCommand="ImageButton_Click" /><br />

        <asp:Label ID="Message" runat="server"></asp:Label></div>

    </form>

Картинки этого примера есть в поставке Visual Studio 2005 Microsoft Visual Studio 8\Common7\IDE\ProjectTemplates\Web\CSharp\1033. Скопируйте их в папку Images вашего проекта и добавьте их в проект.

Эту функцию вставьте в файл отделенного кода:

    protected void ImageButton_Click(Object sender, CommandEventArgs e)

    {

        switch (e.CommandName)

        {

            case "create":

 

                // Insert code to create.

                Message.Text = "Creating ";

                break;

 

            case "add":

 

                // Insert code to add.

                Message.Text = "Adding ";

                break;

            case "cancel":

 

                // Insert code to cancel.

                Message.Text = "canceling";

                break;

        }

 

    }

У первой кнопки установлено свойство ToolTip. Посмотрите страницу в Internet Explorer. Окно с подсказкой появится при наведении на эту кнопку. А вот Opera выводит подсказку для всех кнопок. Но для тех, в которых установлен ToolTip, он выводится на первой строчке. Во второй — адрес.

http://www.intuit.ru/department/se/aspdotnet/4/4_1.png


Рис. 4.1. 

У кнопки также показано использование свойства OnClientClick. Оно задает клиентский сценарий, который будет исполняться при нажатии на кнопку без обращения к серверу. Здесь это функция alert языка Javascript — вызов окна с уведомлением.

HyperLink и LinkButton

HyperLink — гиперссылка обычная или с картинкой. Они позволяют передвигаться по сайту или давать ссылку на другие сайты:

<asp:HyperLink ID="HyperLink1" runat="server"

NavigateUrl="~/Customer.aspx">HyperLink</asp:HyperLink>

Знак ~ обозначает корневой каталог текущего сайта.

LinkButton — это кнопка, которая выглядит как гиперссылка. Нажатие на нее приводит к перезагрузке страницы. В свойстве PostBackUrl можно задать адрес страницы, которая будет обрабатывать текущую.

BulletedList

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

Тип списка определяется свойством BulletStyle. Список может быть пронумерован цифрами, буквами или латинскими числами либо же помечен маркерами разных форм:

<asp:BulletedList ID="BulletedList1" runat="server"

BulletStyle="Numbered">

    <asp:ListItem>Чебурашка</asp:ListItem>

    <asp:ListItem>Крокодил Гена</asp:ListItem>

    <asp:ListItem>Шапокляк</asp:ListItem>

    </asp:BulletedList>

Выглядит на странице как

  1. Чебурашка
  2. Крокодил Гена
  3. Шапокляк

А его HTML-код

  <ol id="BulletedList1" style="list-style-type:decimal;">

   <li>Чебурашка</li><li>Крокодил Гена</li><li>Шапокляк</li>

Если свойство BulletStyle поменять на "Circle", то будет сгененирован ненумерованный список:

 <ul id="BulletedList1" style="list-style-type:circle;">

   <li>Чебурашка</li><li>Крокодил Гена</li><li>Шапокляк</li>

При значении CustomImage необходимо задать картинку в свойстве BulletImageUrl. Элементами списка могут быть гиперссылки и кнопки-гиперссылки:

        <asp:BulletedList ID="BulletedListLinks" runat="server"

BulletStyle ="Circle" DisplayMode="HyperLink">

        <asp:ListItem Value="http://chebur.polyn.kiae.su">Чебурашка

</asp:ListItem>

        <asp:ListItem Value="http://gena.crocodile.net">Крокодил

Гена</asp:ListItem>

        <asp:ListItem Value="http://chapeauclack.com">Шапокляк

</asp:ListItem>

        </asp:BulletedList>

Сами гиперссылки следует записывать в атрибут ListItem Value.

Тип LinkButton превращает пункты списка в гиперссылки, которые вызывают перезагрузку страницы. Обработчик нажатия на кнопки принимает аргумент, в котором содержится информация об индексе нажатого пункта:

        <asp:BulletedList ID="BulletedListLinks" runat="server"

BulletStyle ="Circle" DisplayMode="LinkButton"

OnClick="BulletedListLinks_Click">

        <asp:ListItem Value="Red">Красный</asp:ListItem>

        <asp:ListItem Value="Blue">Синий</asp:ListItem>

        <asp:ListItem Value="Green">Зеленый</asp:ListItem>

        </asp:BulletedList>

 

    protected void BulletedListLinks_Click(object sender,

BulletedListEventArgs e)

    {

        switch (e.Index)

        {

            case 0: BulletedListLinks.BackColor =

System.Drawing.Color.LightCoral;

                break;

            case 1: BulletedListLinks.BackColor =

System.Drawing.Color.Aqua;

                break;

            case 2: BulletedListLinks.BackColor =

System.Drawing.Color.LightGreen;

                break;

        }

    }

Literal

Если не требуется менять значение текста программно, можно использовать элемент управления Literal. В таком случае текст будет выводиться "как есть", без тегов <span>. Этот класс наследуется не от WebControl, а от Control, поэтому его можно ставить вне формы. Соответственно у него нет свойств, отвечающих за внешний вид и стиль. Это не страшно, стиль может быть определен в контейнере, в который он включен, — div или Panel. Зато есть интересное свойство Mode. Попробуем на примере:

<asp:Literal ID="Literal1" Runat="server" Mode="Encode"

Text="<b>Here is some text</b>"></asp:Literal>

Mode="Encode" кодирует текст так, чтобы в браузере был виден именно этот HTML-код, заменяя специальные символы разметки CER-последовательностями:

&lt;b&gt;Label&lt;/b&gt;

Это полезно, если бывает нужно вывести код, и не только HTML. На некоторых сайтах, где есть учебники по С++ в plain text, в примерах кода встречается строка

#include

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

Table

Таблицы в HTML очень распространены, так как они позволяют позиционировать элементы на странице. Серверный элемент управления задается тегами <asp:Table ID="Table1" runat="server"></asp:Table>. Мощь ASP .NET проявляется при динамическом создании таблицы. Этот элемент управления в ASP .NET используется реже, так как элемент DataGrid позволяет получить те же результаты, имея к тому же привязку к данным.

Свойство Rows таблицы является контейнером строк — элементов TableRow, а они, в свою очередь, имеют свойство Cell — коллекцию элементов TableCell. Сам TableCell — контейнер любых элементов управления. Их нельзя вставлять в таблицу иначе, чем в один из элементов TableCell. Текст в ячейку можно записать через свойство Cell.Text или вставкой элемента Literal.

Строки таблицы могут быть также типа TableHeaderRow и TableFooterRow. Такие строки всегда отображаются на мобильных устройствах с небольшим экраном, даже если таблица большая и для ее просмотра нужна прокрутка. Ячейки таблицы могут быть типа TableHeaderCell — наследника TableCell. Текст в них отображается выделенным полужирным шрифтом и центрирован.

В ASP .NET 2.0 у элемента появилась возможность задавать заголовки с помощью свойства Caption. Местоположение заголовка определяется свойством CaptionAlign. При значении Bottom он будет находится под таблицей. При остальных значениях заголовок находится где ему положено — наверху, Left и Right просто выравнивают у левого или правого края.

Этот пример иллюстрирует создание таблицы в программном режиме. Игра "Найди число" тренирует внимание и память. Вначале игрок вводит размер таблицы n. Программа генерирует квадратную таблицу, где числа от 1 до n^2 написаны на кнопках и перемешаны в случайном порядке.

http://www.intuit.ru/department/se/aspdotnet/4/4_2.png


Рис. 4.2. 

    protected void Page_Load(object sender, EventArgs e)

    {

        int tableSize = 5;

        if (!Page.IsPostBack)

        {

            int[,] numbers = new int[tableSize, tableSize];

            for (int i = 0; i < tableSize; i++)

                for (int j = 0; j < tableSize; j++)

                {

                    numbers[i, j] = i * tableSize + j+1;

                }

            int current = tableSize * tableSize;

            Random r = new Random();

            for (int i = 0; i < tableSize * tableSize / 2; i++)

            {

                //swap i and number

                int number = r.Next(1, current);

                int t = numbers[number / tableSize, number % tableSize];

                numbers[number / tableSize, number % number] =

numbers[i / tableSize, i % tableSize];

                numbers[i / tableSize, i % tableSize] = t;

                current--;

            }

            Table Table1 = new Table();

            Table1.CellSpacing = 0;

            Table1.CellPadding = 0;

            Table1.BorderWidth = 2;

            Table1.GridLines = GridLines.Both;

            for (int i = 0; i < tableSize; i++)

            {

                TableRow row = new TableRow();

                for (int j = 0; j < tableSize; j++)

                {

                    TableCell cell = new TableCell();

                    Button button = new Button();

                    button.Text = numbers[i, j].ToString();

                    button.OnClientClick = "return false";

                    button.Width = 26;

                    button.Height = 26;

                    cell.Controls.Add(button);

                    cell.Height = 26;

                    cell.Width = 26;

                    row.Cells.Add(cell);

                }

                Table1.Rows.Add(row);

            }

            form1.Controls.Add(Table1);

        }

    }

}

Calendar

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

Calendar имеет множество свойств.

CellPadding

"Набивка" (расстояние между границами клетки и ее содержимым)

CellSpacing

Расстояние между клетками

DayNameFormat

Способ написания названий дней недели. Может принимать значения FirstLetter, FirstTwoLetters, Full, Short

FirstDayOfWeek

Для задания первого дня недели, Default — установки, принятые в системе

NextPrevFormat

Показ названий предыдущего и последующего месяцев. FullMonth — полное название, ShortMonth — первые 3 буквы, CustomText — любой текст, определенный программистом

SelectionMode

Способ выбора даты. Доступны Day, DayWeek, DayWeekMonth и None

ShowDayHeader

Показывать ли названия дней недели (да по умолчанию)

ShowGridLines

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

ShowTitle

Показывать ли заголовок (нет по умолчанию)

TitleFormat

MonthYear, Month

TodaysDate

Какая дата будет выбрана текущей. По умолчанию — дата на сервере

VisibleDate

Месяц, который будет показан в календаре

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

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

<%@ Page Language="C#" %>

 

<script runat="server">

    void Page_Load(Object sender, EventArgs e)

    {

        if (Page.IsPostBack)

        {

            TextToUser.Text = "Вы поедете " +

                calVoyage.SelectedDate.ToLongDateString();

 

        }

    }

 

    void calSelectChange(Object sender, EventArgs e)

    {

        if (calVoyage.SelectedDate > DateTime.Today)

        {

            TextToUser.Text = "Вы действительно хотите отпра-

виться в путешествие " +

calVoyage.SelectedDate.ToShortDateString() + "?";

            Button ok = new Button();

            ok.Width = 100;

            ok.Text = "Да";

            form1.Controls.Add(ok);

        }

        else

           TextToUser.Text="Выберите будущую дату";

    }

 

</script>

 

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Пример календаря</title>

</head>

<body>

    <form runat="server" id="form1">

        <asp:Calendar ID="calVoyage" runat="server"

BackColor="lightgreen" CellPadding="3"

            CellSpacing="3" NextPrevFormat="FullMonth"

SelectionMode="DayWeekMonth " OnSelectionChanged="calSelectChange" />

        <asp:Label ID="TextToUser" runat="server" Font-

Bold="True" Font-Underline="False" /><br>

    </form>

</body>

</html>

Если мы посмотрим на HTML-код страницы, которая сгенерирована этой программой, то увидим довольно объемный текст, в том числе функцию на JavaScript и большую таблицу, каждая ячейка которой ссылается на эту функцию. Было бы непросто написать все это самим.

Свойства SelectMonthText и SelectWeekText задают символы HTML, по умолчанию &gt; и &gt;&gt; эта последовательность отображается в символы >.

С событием выбора даты OnSelectionChanged связан обработчик calSelectChange. В нем мы сначала проверяем, является ли выбранная дата будущей по отношению к сегодняшнему дню. Если да, то создается новая кнопка для подтвержения выбранной даты.

Такая строка в Page_Load —

    this.Culture=new CultureInfo("th-TH").ToString();

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

%@Import Namespace= "System.Globalization" %

Свойство SelectionMode, равное DayWeekMonth, позволяет выбрать как конкретный день, так и неделю или месяц. Как же получить выбранный диапазон дат? SelectedDate возвращает только первый день диапазона. Для этого существует коллекция Calendar1.SelectedDates:

TextToUser.Text = "Вы пробудете в путешествии ";

            for (int i = 0; i < calVoyage.SelectedDates.Count;

i++)

            {

                TextToUser.Text +=

calVoyage.SelectedDates[i].ToShortDateString() +

                "<br>";

            }

А еще элегантнее будет написать так:

foreach ( DateTime i in calVoyage.SelectedDates)

            {

              TextToUser.Text += i.ToShortDateString() + "<br>";

            }

Можно запретить пользователю выбирать прошлую дату. Для этого можно воспользоваться свойством IsSelectable:

if (e.Day.Date < DateTime.Now) {

e.Day.IsSelectable = false;

}

Автоформатирование календаря

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

В Visual Studio 2005 можно поменять внешний вид календаря с помощью предопределенных шаблонов. У многих элементов управления в режиме дизайна есть "умные ярлычки" (smart tags). В них есть ссылка на диалог автоформатирования календаря. При этом уже сделанные изменения свойств сохранятся.

Внешний вид календаря можно менять в обработчике события DayRender. У аргумента обработчика DayRenderEventArgs два свойства: Cell с типом TableCell — наследника WebControl и Day типа CalendarDay. В Cell можно добавлять новые элементы, но только такие, которые не запускают событий:

    protected void calVoyage_DayRender(object sender,

DayRenderEventArgs e)

    {

        if (e.Day.Date.Day == 8 && e.Day.Date.Month == 3)

        {

            e.Cell.BorderColor = System.Drawing.Color.Red;

            e.Cell.BorderWidth = 4;

            e.Cell.Controls.Add(new LiteralControl("<br>Без жен-

щин жить нельзя"));

        }

    }

В этом примере проверяется день, и если это 8 марта, то вокруг него рисуется красная рамка. А еще добавляется элемент LiteralControl с текстом, соответствующим моменту.

http://www.intuit.ru/department/se/aspdotnet/4/4_3.png


Рис. 4.3. 

Отправка данных другой странице

В ASP .NET 1.1 не разрешалась отправка данных между страницами. В ASP .NET 2.0 элементы управления имеют свойство PostBackUrl, где можно указать, какой странице система должна передать Web-форму, если отправление данных на сервер инициировано этим элементом управления.

Через свойство PreviousPage страницы можно выяснить, какая страница была источником постбэка нашей страницы.

На первой странице рисуется календарь:

<%@ Page Language="C#" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<script runat="server">
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
 <title>Первая страница</title>
</head>
<body>
<form id = "form1" runat= "server">
Ваше имя:<br />
<asp:Textbox ID="TextBox1" Runat="server">
</asp:Textbox>
<p>
Желаемая дата вылета?<br />
<asp:Calendar ID="Calendar1" Runat="server"></asp:Calendar></p>
<br />
<asp:Button ID="Button2" Runat="server" Text="Submit page to 
Page2.aspx"
PostBackUrl="Page2.aspx" />
<p>
<asp:Label ID="Label1" Runat="server"></asp:Label></p>
</form>
</body>
</html>

А на второй читаются значения первой формы:

<%@ Page Language="C#" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<script runat="server">
protected void Page_Load(object sender, System.EventArgs e)
{
    if (PreviousPage != null)
    {
        TextBox pp_Textbox1;
        Calendar pp_Calendar1;
        pp_Textbox1 = 
(TextBox)PreviousPage.FindControl("Textbox1");
        pp_Calendar1 = 
(Calendar)PreviousPage.FindControl("Calendar1");
        Label1.Text = "Здравствуйте, " + pp_Textbox1.Text + 
"!<br />" +
          "Вы выбрали: " + 
pp_Calendar1.SelectedDate.ToShortDateString();
    }
}
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Вторая страница</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:Label ID="Label1" Runat="server"></asp:Label>
    </div>
    </form>
</body>
</html>

Заключение

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

Свойство AutoPostBack

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

Если AutoPostBack установлен для элемента управления TextBox, то для него будет вызываться событие TextChanged, как только поле потеряет фокус или будет нажата клавиша Enter. Чтобы это свойство работало, браузер должен поддерживать ECMAScript (стандарт JavaScript, принятый Европейской ассоциацией производителей компьютеров).

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

Brand

Buick

Chevrolet

Pontiac

Toyota

Mileage

Features

Power seat

Buick

Century

Impala

Grand Am

Avalon

0-10000

Leather seat

Chevrolet

LeSabre

Malibu

Grand Prix

Camry

10000-20000

Sun roof

Pontiac

Park Avenue

Metro

Montana

Camry Solara

20000-30000

CD player

Toyota

Regal

Prizm

Sunfire

Celica

30000 and more

ABS

Все данные, используемые на этой странице, собраны в таблицу. Для хранения такой таблицы существует класс DataTable. Таблица состоит из столбцов — DataColumn и строк DataRow. Класс DataView позволяет создавать различные представления данных таблицы. Первый столбец служит источником данных списка марок. В зависимости от выбранной модели, в список моделей загружается одна из 2-5 колонок.

Вначале создается таблица:

Cars = new DataTable();
Cars.Columns.Add(new DataColumn("Brand", typeof(string)));

Здесь вызывается один из конструкторов DataColumn. Первый аргумент — название колонки, второй — тип:

CarRow = Cars.NewRow();

Создается новая строка таблицы. Ячейка таблицы задается с помощью индекса строки:

CarRow[6]= "Power seat";

И строка добавляется в таблицу:

Cars.Rows.Add(CarRow);

У выпадающего списка марок установлено свойство AutoPostBack. Это значит, что страница автоматически подается на сервер, когда в этом списке меняется выбранный элемент.

В обработчике выбора нового элемента вначале выясняется, какой элемент выбран:

string selected = DropDownList1.SelectedItem.Value;

В операторе switch происходит переключение второго списка на один из столбцов таблицы заданием свойств DataTextField и DataValueField, где DataTextField — текст, отображаемый в списке, а DataValueField — выбранное значение. В данном случае, как часто бывает, они одинаковы.

Привязка к данным

Некоторые элементы управления: списки, таблицы и другие — имеют свойство DataSource, которое отвечает за привязку к данным. Тип этого свойства — object, то есть он может быть любым, но должен реализовывать интерфейс IEnumerable. Часто значениями этого свойства назначают коллекции. В таком случае нет нужды добавлять значения вручную. Свойство DataSource может быть привязано к коллекциям, поддерживающим интерфейсы IEnumerable, ICollection или IListSource. Источником данных также могут быть XML-файлы, базы данных. Вызовом метода DataBind данные привязываются к элементу управления. Метод Page.DataBind вызывает привязку данных у всех элементов на странице.

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

    void Page_Load()
    {
        ArrayList ContinentArrayList = new ArrayList();
        ContinentArrayList.Add("Worldwide");
        ContinentArrayList.Add("America");
        ContinentArrayList.Add("Africa");
        ContinentArrayList.Insert(1, "Asia-Pacific");
        ContinentDropDownList.DataSource = ContinentArrayList;
        ContinentDropDownList.DataBind();
    } //End Page_Load()
....
<asp:DropDownList id="ContinentDropDownList" runat="server" />

Можно использовать в качестве источника данных хэш-таблицы (Hashtable). Хэш-таблицы — это структуры данных, которые были придуманы давно (см. том 3 "Искусства программирования" Д. Кнута), но программисты долгое время были вынуждены реализовывать их вручную. В языке PHP обычный массив и есть хэш-таблица. В библиотеке STL для языка С++ тоже есть тип map, в котором данные хранятся таким способом. Хэш-таблицы позволяют очень быстро найти значение по ключу. Индекс в коллекции вычисляется как простая хэш-функция ключа. В C# ключи используются как индексаторы. Используйте Hashtable, если в программе часто осуществляется поиск. Вставка и удаление происходят в нем медленно. Ключи могут быть произвольного типа. В классе Object определен виртуальный метод GetHashCode, он и применяется в Hashtable:

<%@ Page Language="C#" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void calSelectChange(Object sender, EventArgs e)
{
     lblShow.Visible = false;
     Hashtable hshDays = new Hashtable();
     hshDays[Convert.ToDateTime("2/6/2006")] = "Экзамен по алгеб-
ре";
     hshDays[Convert.ToDateTime("3/6/2006")] = "Экзамен по С#";
     hshDays[Convert.ToDateTime("4/6/2006")] = "Начало изучения 
курса ASP.NET";
     hshDays[Convert.ToDateTime("1/6/2006")] = "День защиты де-
тей";
     DateTime datDateIn;
     datDateIn = calDays.SelectedDate;
     if (Page.IsPostBack)
     {
         lblShow.Text = "На этот день назначен: ";
         lblShow.Text += hshDays[datDateIn];
         if (hshDays[datDateIn] == null)
             lblShow.Text = "Ничего не назначено";
         lblShow.Visible = true;
     }
}
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Попробуем хэш-таблицу</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <h3>Ежедневник
</h3>
Введите дату между 1/6/2006 и 30/6/2006
<asp:Calendar id="calDays" runat="server" 
OnSelectionChanged="calSelectChange"
VisibleDate="06/06/2006"
></asp:Calendar>
<br />
<br />
<asp:Label id="lblShow" runat="server"></asp:Label>
    </div>
    </form>
</body>
</html>

Здесь ключом хэш-таблицы является дата. Convert.ToDateTime конвертирует строку в тип даты. VisibleDate гарантирует, что на календаре будет июнь 2006 года. Если значений по ключу в таблице нет, то индексатор просто возвращает null. Значения можно вводить в любом порядке.

Хотелось бы добавить в страницу новую возможность введения новых записей. Можно ввести новые элементы управления — строку ввода и кнопку для подачи данных. При обработке нажатия на кнопку добавим в хэш-таблицу новое значение:

    void Button1_Click(object sender, EventArgs e)
    {
        hshDays[calDays.SelectedDate]=TextBox1.Text;
    }

Эта страница не работает. Дело в том, что страница загружается заново, когда меняется дата. Хэш-таблица создается заново, и введенные в нее значения теряются. Как же решить эту проблему? Сделаем хэш-таблицу статической переменной:

static Hashtable hshDays;
void calSelectChange(Object sender, EventArgs e)
{
    DateTime datDateIn = calDays.SelectedDate;
 
    lblShow.Text = "На этот день назначен: ";
    lblShow.Text += hshDays[datDateIn];
    if (hshDays[datDateIn] == "")
        lblShow.Text = "Ничего не назначено";
}
void Page_Init()
{
    if (!Page.IsPostBack)
    {
        hshDays=new Hashtable();
        hshDays[Convert.ToDateTime("2/6/2006")] = "Экзамен по ал-
гебре";
        hshDays[Convert.ToDateTime("3/6/2006")] = "Экзамен по 
С#";
        hshDays[Convert.ToDateTime("4/6/2006")] = "Начало изуче-
ния курса ASP.NET";
        hshDays[Convert.ToDateTime("1/6/2006")] = "День защиты 
детей";
        Session["Diary"]= hshDays;
     }
}
 
 
void Record(Object sender, EventArgs e)
{
    DateTime datDateIn = calDays.SelectedDate;
    hshDays[datDateIn]= Entrance.Text;
    lblShow.Text = hshDays[datDateIn].ToString();
}

Классы проверки данных (валидаторы)

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

Прежде чем работать с данными, нужно убедиться, что:

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

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

Получить доступ к валидаторам просто — раскройте в Toolbox вкладку "Validation".

Классы валидаторов образуют иерархию, во главе которой стоит абстрактный класс BaseValidator.

http://www.intuit.ru/department/se/aspdotnet/5/5_1sm.png


увеличить изображение
Рис. 5.1. 

Базовый класс валидаторов сам наследник класса Label, так что по существу все валидаторы — метки, текст в которых становится видимым, когда не выполняются заданные нами условия проверки. По умолчанию текст в валидаторах — красный (вспомните школу и замечания учительницы в тетради). Но, конечно же, этот цвет можно поменять на более приятный. Все валидаторы имеют свойство ControlToValidate. Оно задает тот элемент управления, данные в котором проверяются данным валидатором. Этот элемент должен находиться в одном контейнере с валидатором.

Общие свойства валидаторов

Display

Предоставлять ли место статически или динамически

EnableClientScript

Разрешать ли генерировать клиентский код

ErrorMessage

Текст сообщения об ошибке

IsValid

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

Инициация проверки данных

Проверка всегда инициируется каким-либо событием. Обычно это щелчок на кнопках Button, ImageButton, LinkButton, в которые по умолчанию свойство CausesValidation установлено в True. Можно убрать это свойство для некоторых кнопок, которым оно не нужно, например, для кнопки Cancel.

В примере с автосалоном на странице имеются несколько валидаторов:

<asp:requiredfieldvalidator id="RequiredFieldValidator2" 
runat="server" 
   ErrorMessage="Required" 
   ControlToValidate="DropDownList1">
      </asp:requiredfieldvalidator>

Класс RequiredFieldValidator проверяет, было ли изменено значение в связанном с ним элементе управления. Если, как в данном случае, это выпадающий список — первоначально выбрано пустое значение, но требуется, чтобы пользователь выбрал конкретную марку. Если выбор не был сделан, но кнопка submit была нажата, валидация проваливается и выводится текст, заданный в ErrorMessage или в Text. Валидаторы отображают текст, указанный в свойстве "Text", всегда, когда оно не пусто, а текст, установленный в свойстве "ErrorMessage" — тогда, когда свойство "Text" равно "". Первоначальное значение задается свойством InitialValue. Если это свойство не задано, то проверка проводится на отсутствие значения (например, пустой Textbox).

Для проверки корректности ввода электронного адреса используется класс RegularExpressionValidator:

   <span class="label">Your Email</span><span 
class="label1">(Required)</span>
   <asp:textbox id="TextBox1" runat="server"></asp:textbox>
   <asp:RegularExpressionValidator 
id="RegularExpressionValidator1" runat="server" 
ControlToValidate="TextBox1" ErrorMessage="Not a valid Email" 
ValidationExpression="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-
.]\w+)*"></asp:RegularExpressionValidator>
   <asp:RequiredFieldValidator id="RequiredFieldValidator1" 
runat="server" ControlToValidate="TextBox1" 
ErrorMessage="*"></asp:RequiredFieldValidator>
</span>

ValidationExpression — регулярное выражение, на соответствие которому проходит проверку значение текстового поля. В Visual Studio 2005 предоставляет несколько готовых шаблонов регулярных выражений, которые можно выбрать в окне свойств, — телефонных номеров разных стран, адресов, и, самое полезное, шаблоны электронной почты и адреса в Интернете.

С одним элементом управления может быть связано несколько валидаторов. Например, электронный адрес проверяется и на соответствие шаблону, и на обязательное заполнение.

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

Валидаторы сравнения

CompareValidator сравнивает значение со значением в другом элементе управления или с константой. Также можно проверить, можно ли конвертировать значение в связанном с ним элементе управления в какой-либо тип.

Свойство Operator позволяет установить операцию, посредством которой происходит сравнение: Equal, NotEqual, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual. Значение DataTypeCheck означает проверку на соответствие типу.

Свойство Type может принимать значения String, Integer, Date, Double и Currency.

Свойство ControlToCompare задает элемент управления, с которым происходит сравнение. ValueToCompare задает значение. Из этих двух свойств установленным может быть только одно.

RangeValidator проверяет соответствие введенного значения диапазону, заданному свойствами MinimumValue и MaximumValue:

<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:RangeValidator ID="RangeValidator1" runat="server" 
    ControlToValidate="TextBox1"
    ErrorMessage="Не больше 10 бутылок пива в одни руки" 
    MaximumValue="10" MinimumValue="1" Type="Integer">
</asp:RangeValidator>
ValidationSummary

Класс ValidationSummary позволяет вывести итоговую информацию по всем валидаторам на странице. Она может быть выведена в различной форме:

Информацию можно выводить на странице, а можно в информационном окне, если ShowMessage поставить в True. Для всех валидаторов выводится свойство Error Message, а не текст. Text выводится в самом валидаторе.

Вернемся к странице Registration.aspx. Добавим в него еще одно поле для ввода пароля:

<form runat="server" id="input">
    <asp:Label ID="Label1" runat="server" Text="Введите имя:" 
Width="140px"></asp:Label>
<asp:TextBox ID="txtName" runat="server" CausesValidation="True" 
/>
    <asp:Label ID="Label2" runat="server" Text="Введите адрес:" 
Width="140px"></asp:Label>
<asp:TextBox id="txtAddress" runat="server" textmode="multiline" 
rows="5"
/>
<br/><br />
    <asp:Label ID="Label3" runat="server" Text="Введите пароль:" 
Width="140px"></asp:Label>
    <asp:TextBox id="txtPassword" runat="server" textmode="password" />
    <br />
<br />
    <asp:Label ID="Label4" runat="server" Text="Повторите пароль" 
Width="140px"></asp:Label>
    <asp:TextBox id="TextBox1" runat="server" textmode="password" 
/><br />
   <asp:Button ID="Button1" runat="server" Text="Submit" />
</form>

Свойство CausesValidation работает, когда элемент управления теряет фокус. В таком случае связанный с ним валидатор показывает значение своего свойства Text.

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

Перетащите RequiredFieldValidator и бросьте его на форму. ControlToValidate установите в txtName. Второй валидаторRequired FieldValidator для пароля. Третий — CompareValidator, который сравнивает значение паролей:

<asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
ControlToValidate="txtName"
    Display="Static" ErrorMessage="Имя необходимо ввести" 
runat="server" >*</asp:RequiredFieldValidator>
 
        <asp:RequiredFieldValidator ID="RequiredFieldValidator2" 
runat="server" ErrorMessage="Пароль не должен быть пустым" 
ControlToValidate="txtPassword1">*</asp:RequiredFieldValidator>
 
<asp:CompareValidator ID="CompareValidator1" runat="server"
    ControlToValidate="txtPassword1"
    ErrorMessage="Пароли должны совпадать!" 
ControlToCompare="txtPassword2"></asp:CompareValidator><br />

А также один ValidationSummary:

<asp:ValidationSummary ID="ValidationSummary1" runat="server" />

Поставим кнопку, при нажатии на которую будет происходить проверка:

<asp:Button ID="Button1" runat="server" Text="Валидация" 
OnClick="Validate_Click" />

Обработчик нажатия на кнопку подтверждает ввод, если валидация прошла успешно:

    protected void Validate_Click(object sender, EventArgs e)
    {
        if (Page.IsValid)
        {
            lblName.Text = "";
            lblAddress.Text = "";
            lblPassword.Text = "";
            input.Visible = false;
            if (txtName.Text != "")
                lblName.Text = "Вы ввели имя: " + txtName.Text;
            if (txtAddress.Text != "")
                lblAddress.Text = "Вы ввели адрес: " +
            txtAddress.Text;
            if (txtPassword1.Text != "")
              lblPassword.Text = "Вы ввели пароль: " +
                 txtPassword1.Text + "<br>Спасибо за регистрацию!";
        }
    }

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

CustomValidator

Если нужно сделать такую проверку, которую не получается осуществить с помощью стандартных валидаторов, в игру вступает Custom Validator. В классе CustomValidator можно написать любую функцию, которая будет проверять значения как на стороне сервера, так и у клиента. Классический пример — проверка числа на четность.

Напишем пользовательский валидатор, который будет проверять пароль на длину — не меньше 5 символов:

<head>
    <title>Регистрация нового пользователя</title>
<script language="JavaScript">
function validatePassword(oSrc, args)
{
    args.IsValid = (args.Value.length > 5); 
}
</script>
 
</head>
    <asp:CustomValidator ID="CustomValidator1" runat="server" 
        ControlToValidate="txtPassword1"
        ErrorMessage="Слишком короткий пароль" Display="Static"
        ClientValidationFunction="validatePassword" 
>*</asp:CustomValidator>

Проверка происходит на стороне клиента функцией на JavaScript validatePassword. Для этого надо в свойстве ClientValidationFunction записать имя функции. В сгенерированном коде к кнопке привязан такой код:

<input type="submit" name="Button1" value="Button" 
onclick="javascript:WebForm_DoPostBackWithOptions(new 
WebForm_PostBackOptions("Button1", "true", "false", "false"))" 
id="Button1" />

Известно, что если функция, связанная с onclick, вернет логическое значение false, форма не будет отправлена на сервер:

    <asp:CustomValidator ID="CustomValidator1" runat="server" 
        ControlToValidate="txtPassword1"
        ErrorMessage="Слишком короткий пароль" Display="Static"
        OnServerValidate="ServerValidate" 
>*</asp:CustomValidator><br />

Произведем такую же валидацию на сервере. Чтобы запустить проверку на сервере, используется свойство OnServerValidate. Функция, которая указана в ней, входит в класс страницы и должна быть написана на C#:

    void ServerValidate(object source, ServerValidateEventArgs args)
    {
        string password = args.Value.ToString();
        int len = password.Length;
        args.IsValid = (len >= 5);
    }

ServerValidate вызывается после события Page_Load, поэтому нельзя, как в лекции 3, выводить результаты в Page_Load.

Можно также одновременно проводить и клиентскую, и серверную валидацию:

    <asp:CustomValidator ID="CustomValidator1" runat="server" 
        ControlToValidate="txtPassword1"
        ErrorMessage="Слишком короткий пароль" Display="Static"
        OnServerValidate="ServerValidate" 
        ClientValidationFunction="validatePassword" 
>*</asp:CustomValidator>

Чтобы отключить возможность генерации клиентского кода для всех валидаторов, можно написать

    void Page_Load()
    {
        foreach (BaseValidator bv in Page.Validators)
        {
            bv.EnableClientScript = false;
        }
    }

Для отображения сообщения об ошибке можно использовать звуки и картинки. Для этого в свойство ErrorMessage нужно записать не текст, а соответствующие теги HTML, например

ErrorMessage=’<img src="error.gif">’
Группы валидации

Иногда бывает нужно иметь на странице несколько кнопок, и при нажатии на каждую вводится информация из логически взаимосвязанных групп элементов управления. Поэтому должны проверяться значения только из этой группы. У всех валидаторов и элементов управления, через которые возможен ввод информации, есть свойство ValidationGroup. Функцию Page.Validate() тоже можно использовать с таким параметром. Если происходит нажатие на кнопку с установленным ValidationGroup, запускается проверка тех валидаторов, у которых это свойство такое же.

Заключение

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

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

Для хранения данных чаще всего используются СУБД (системы управления базами данных). Как уже говорилось, в ASP .NET 2.0 работа с данными происходит через ADO .NET 2.0— часть .NET, разработанная специально для доступа к базам данных или XML-файлам.

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

Данные в СУБД хранятся в таблицах. Таблица состоит из полей и записей. Запись — единица хранения данных, строка таблицы. Например, в одной записи хранятся сведения об одном человеке. Поля — это столбцы таблицы для хранения конкретного вида информации. Базы данных называются реляционными, потому что таблицы в них связаны определенным образом.

Представления (View) создаются на основе одной или нескольких таблиц с помощью фильтрации, объединения, сортировки и группирования.

Для наглядности рассмотрим эти понятия на примере. В свое время я работала с базой данных кадров одного предприятия. Как создать такую базу? Прежде всего нам нужна таблица для хранения данных о сотрудниках.

ID

Имя

Фамилия

Отчество

Дата рождения

Дата приема

Должность

1

Петр

Васечкин

Иванович

1965

2001

Завхоз

2

Василий

Петров

Сидорович

1977

2003

Программист

и т.д. Таблица может иметь тысячи записей.

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

Должности

ID

Название должности

Минимальный оклад

1

Директор

1

2

Завхоз

10

3

Программист

20

4

Уборщица

100

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

Перемещения

ID

Сотрудник

Должность

Назначение

Дата

1235

123

10

11

20.06.06

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

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

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

SELECT Employees.LastName, Employees.FirstName, Titles.Title,

Titles_1.Title, Promotions.PromotionDate FROM Titles AS Titles_1

INNER JOIN ((Promotions INNER JOIN Titles ON

Promotions.TitleBefore = Titles.id) INNER JOIN Employees ON

Promotions.EmployeeID = Employees.EmployeeID) ON Titles_1.id =

Promotions.TitleAfter;

Множество таблиц данных, связанных отношениями, составляют базу данных. На сервере СУБД может храниться множество баз данных.

Подробнее о теории баз данных можно прочитать в других курсах. Перейдем к конкретным примерам связывания с базами данных на web-страницах.

У каждого пользователя Windows наверняка имеется программа Access. Это однопользовательская СУБД, в которой модель безопасности не так сильна. В одном файле Access хранятся как данные, так и интерфейс в виде форм и отчетов. Можно создавать модули на VBA (Visual Basic for Application). Профессиональные разработчики пользуются более мощными программами. По "серьезности" СУБД от Microsoft идут в порядке возрастания: Access — FoxPro — MS SQL. MS SQL не позволяет создавать формы, а занимается хранением и защитой данных на профессиональном уровне. Visual Studio 2005 (и VWD) при инсталляции устанавливает MS SQL Express. Он будет запускаться автоматически в виде сервиса Windows.

Для работы с базами данных используется язык структурированных запросов — SQL (Structured Query Language). Команды этого языка называются запросами. Запросы служат для получения данных, для создания и изменения структуры таблиц, добавления, удаления и обновления записей и многого другого. Последовательность команд может храниться прямо на сервере СУБД в виде хранимой процедуры. Нужно стараться всегда пользоваться хранимыми процедурами, а не писать команды самим. Главное их преимущество — скорость работы и инкапсуляция бизнес-логики. Хранятся они на сервере в уже откомпилированном виде, в то время как простой переданный набор команд SQL проходит через стадию компиляции.

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

Для конфигурирования источников данных на вашем компьютере зайдите в Control Panel, Administrative Tools, Data Sources (ODBC).

Мы видим, что ODBC при наличии нужного драйвера позволяет связываться с различными базами данных — Access, FoxPro, Oracle, Microsoft SQL, MySQL, SAP, DB2. Если в файле Excel создать именованную таблицу, ODBC способен ее распознать и работать как с таблицей базы данных.

Веб-проект в Visual Studio 2005 содержит предопределенную папку App_Data. В ней могут храниться файлы с данными, которые используются в приложении. Это могут быть файлы .mdf (MS SQL), .mdb (Microsoft Access), .xml и другие.

ADO .NET 2.0

ADO .NET — это набор классов для работы с внешними данными. В новой версии .NET 2.0 он был расширен новыми свойствами и тоже получил номер 2.0.

Соединение в ADO.NET может происходить с помощью различных провайдеров. В настоящее время рекомендуется работать с помощью провайдера MS SQL или Oracle. Эти провайдеры сами написаны на управляемом коде .NET. Еще один провайдер, OLEDB, позволяет получить доступ к другим источникам данных — Access, Excel, MySql, SAP. Провайдер OLEDB написан на неуправляемом коде, но может работать вместе с .NET.

Классы ADO .NET объединены в несколько пространств имен.

System.Data — это ядро ADO .NET. Оно содержит классы, необходимые для связи посредством любых провайдеров данных. Эти классы представляют таблицы, строки, столбцы, DataSet (множество взаимосвязанных таблиц). Там определены интерфейсы (в смысле языка C#) соединений с базами данных, команд, адаптеров данных.

System.Data.Common — базовые классы для всех провайдеров данных — DbConnection, DbCommand, DbDataAdapter.

В System.Data.OleDb находятся классы, позволяющие работать с источниками данных OleDb, в том числе с MS SQL версии 6.0 и ниже. Там находятся такие классы, как OleDbConnection, OleDbDataAdapter и OleDbCommand.

System.Data.Odbc содержит классы, которые работают с источниками данных ODBC посредством провайдера .NET ODBC. Классы имеют аналогичные имена с префиксом Odbc.

System.Data.SqlClient. Здесь определен провайдер данных для СУБД SQL Server версии 7.0 и выше. Содержатся классы SqlConnection, SqlTransaction, SqlCommand и другие.

В System.Data.SqlTypes находятся классы, представляющие типы данных СУБД SQL Server.

Классы ADO .NET делятся на 3 типа. Классы типа Disconnected определяют базовую структуру данных, например, DataTable. Они независимы от каких-либо провайдеров данных и могут создаваться и заселяться данными непосредственно в программе. Классы Shared — базовые и общие для всех провайдеров. Классы Data Provider — специфические для разных провайдеров.

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

Все провайдеры данных содержат классы соединений, адаптеров, команд. Схема типичной программы в ADO .NET следующая:

1. Вначале создается соединение с базой данных — класс Connection, который обеспечивается необходимой информацией — строкой соединения.

2. Создается объект Command и задается команда, которую необходимо выполнить в данной СУБД. Эта команда может быть запросом SQL или исполняемой процедурой. Нужно задать параметры этой команды, если они имеются.

3. Если команда не возвращает данных, она просто выполняется с помощью одного из методов Execute. Например, это может быть удаление или обновление данных таблицы.

4. Если команда возвращает выборку данных, их необходимо куда-то поместить. Решите, нужно ли вам получить данные для последующего использования без связи с базой данных или же нужно просто быстро выполнить команду. В первом случае нужно создать класс DataAdapter и с его помощью сохранить данные в DataSet или в DataTable. Во втором случае создается класс DataReader, который требует сохранять соединение на все время работы, хранит выборку только для чтения и позволяет двигаться только вперед. Зато чтение с помощью DataReader выполняется в несколько раз быстрее, чем в DataAdapter.

5. Задать полученный DataSet или DataReader как источник данных элемента управления или вывести их на страницу другим способом.

Объект Connection

Объект Connection для соединения с базой данных нуждается в строке соединения для указания пути к СУБД и входа в систему. Свойства класса Connection показаны в таблице. OleDbConnection, SqlConnection, OdbcConnection — наследники класса Connection, специфические для провайдеров OleDb, MS SQL ODBC соответственно.

Свойство

Описание

DataSource

Путь к базе данных в файловой системе при использовании Oledb, имя экземпляра базы сервера при использовании SqlConnection

Database

Возвращает имя базы данных, используемой в объекте Connection после открытия

State

Возвращает текущее состояние соединения. Возможные значения — Broken, Closed, Connecting, Executing, Fetching и Open

ConnectionString

Строка соединения с СУБД

Все свойства, кроме ConnectionString, — только для чтения.

Использование объекта Command

Объект Command исполняет запрос SQL, который может быть в форме встроенного текста, процедуры сервера или прямого доступа к таблице. Если это запрос на выборку данных SELECT, то данные обычно помещаются в DataSet или в DataReader. Методы и свойства определены в абстрактном классе DbCommand (через интерфейс IDbCommand), и их реализуют частные ненаследуемые классы OleDbCommand, SqlCommand, OdbcCommand.

Свойство CommandType может принимать значения из перечисления CommandType. По умолчанию это Text, то есть выполняется непосредственно текст команды SQL, который записан в свойстве Command. TableDirect означает, что в результате выполнения команды будет возвращено все содержание таблицы. StoredProcedure означает, что в Command находится имя процедуры сервера, которая и будет выполняться.

Свойство CommandText хранит текст запроса SQL или имя серверной процедуры.

CommandTimeout задает время ожидания ответа, по умолчанию равное 30 секундам. Если команда не выполнится в течение этого времени, будет выброшено исключение.

Процедуры сервера нуждаются в параметрах. Они хранятся в коллекции Parameters и имеют тип SqlParameter. Текстовые команды также могут получать параметры, перед которыми ставится префикс @. Например:

" SELECT * FROM CUSTOMERS WHERE CITY = @CITY AND CONTACTNAME = 
@CONTACT "

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

ExecuteScalar возвращает результат запроса в случае, если это одно-единственное значение. Например, нужно узнать количество заказов конкретного покупателя. Запрос выполняется с помощью команды "Select count * where customerid=1". Ее результат — выборка из одной строки и одного столбца. Ее можно выполнить и с помощью метода ExecuteReader, но ExecuteScalar будет выполняться быстрее. Если запрос возвратит большее количество строк или столбцов, они будут проигнорированы.

ExecuteRow возвращает единственную запись.

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

В результате выполнения метода ExecuteReader объекта Command создается объект DataReader. Всегда закрывайте соединения после использования, иначе оно останется активным и будет занимать ресурсы. Это можно сделать двумя способами. Первый — вызвать перегруженный метод ExecuteReader, который принимает параметр типа CommandBehavior со значением CommandBehavior.CloseConnection. В таком случае необходимо перелистать полученную выборку от начала до конца, и соединение закроется, когда будет достигнут конец. Если вы не хотите прочитать все данные, можете самостоятельно закрыть соединение методом Close:

  public void CreateMySqlDataReader(string mySelectQuery, string 
myConnectionString)
  {
    SqlConnection myConnection = new 
SqlConnection(myConnectionString);
    SqlCommand myCommand = new SqlCommand(mySelectQuery, 
myConnection);
    myCommand.CommandType = CommandType.Text;
    myCommand.Connection.Open();
    SqlDataReader myReader = 
myCommand.ExecuteReader(CommandBehavior.CloseConnection);
    while (myReader.Read())
    {
      Response.Write(myReader.GetString(0) + "<br>");
    }
    myReader.Close();
    myConnection.Close();
  }

Развитые СУБД (теперь и MS Access) поддерживают транзакции. Транзакция — это последовательность команд, которая выполняется как одно целое. Например, при переводе денег сумма вычитается с одного счета и добавляется к другому. Если произойдет только одна из этих операций, банк или его клиенты понесут потери, поэтому важно, чтобы произошли обе операции либо ни одна не произошла. Если на одном из этапов транзакции допущена ошибка, происходит откат (Rollback), то есть отменяются все ранее сделанные операции и база возвращается к состоянию до начала транзакции. Если все успешно, транзакция подтверждается операцией Commit.

Для поддержки транзакций введен класс SqlTransaction и ему подобные. У объекта Command есть свойство Transaction. Метод BeginTransaction объекта Connection заставляет базу данных перейти в режим транзакции.

Кроме того, необходимо всегда заключать программный код, работающий с базами данных, в блоки try/catch, так как работа часто идет с удаленными серверами и могут происходить самые разные ошибки как в сети, так и при работе самого сервера.

При этом выбрасывается исключение SqlException или OleDbException:

  public void RunTransaction(string[] Queries, string 
myConnectionString)
  {
    SqlConnection conn = null;
    SqlTransaction trans = null;
    try
    {
      conn = new SqlConnection(myConnectionString);
      conn.Open();
 
      trans = conn.BeginTransaction();
 
      SqlCommand cmd = new SqlCommand();
      cmd.Connection = conn;
      cmd.Transaction = trans;
      foreach (string Query in Queries)
      {
        cmd.CommandText = Query;
        cmd.ExecuteNonQuery();
      }
      trans.Commit();
    }
    catch (SqlException SqlEx)
    {
      if (trans != null)
      {
        trans.Rollback();
      }
 
      throw new Exception("An error occurred while transaction", 
SqlEx);
      return;
    }
    finally
    {
      if (conn != null)
      {
        conn.Close();
      }
    }
  }

DataAdapter

DbDataAdapter является родительским классом для SqlDataAdapter, OleDbDataAdapter, OdbcDataAdapter. Этот класс содержит 4 объекта типа Command. Классы DataAdapter обеспечивают двусторонний обмен информацией.

SelectCommand — эта команда используется для выборки данных из базы. При этом класс DataTable заполняется данными.

UpdateCommand — обновляет данные (редактирование записей).

InsertCommand — добавление новых записей.

DeleteCommand — команда для удаления записей.

Метод Fill класса DbDataAdapter заполняет объекты DataSet или DataTable данными, прочитанными в результате выполнения команды SelectCommand. Эта команда должна быть запросом SQL типа Select. Если таблицы уже существуют, в него добавляются новые таблицы. Вообще метод Fill перегружен 8 раз. Например, DbDataAdapter.Fill Method (DataSet, String) добавляет в DataSet таблицу с именем, указанным во втором параметре. Если такая таблица уже есть, она обновляется. Доступ к таблице можно получить с помощью ее имени индексатором:

DataTable tblProducts = data.Tables["Products"];

Метод DbDataAdapter.Update записывает в базу данных все изменения, которые произошли в связанном с ним объекте DataSet.

DataSet

DataSet — это класс, содержащий в себе одну или несколько таблиц DataTable и связи между ними. Класс DataSet — это представление в памяти информации, считанной через ADO из баз данных или XML. Он позволяет манипулировать данными после отключения от источника данных.

Коллекция таблиц хранится в свойстве Tables, а отношений — в свойстве Relations.

Основываясь на таблицах DataSet, можно создавать представления — DataView.

Напишем страницу, в которой будут использоваться представленные классы.

База Northwind входит в комплект SDK. Ее можно установить на сервере, запустив командную строку SQLExpress:

sqlcmd -E -S (local)\SQLExpress -i InstNwnd.sql
 
<%@ Page Language="C#" AutoEventWireup="true" 
CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Работа с базой</title>
    </head>
    <body>
      <form id="Form1" runat="server">
        <asp:DropDownList ID="DropDownList1" runat="server">
        </asp:DropDownList>
        <asp:DataGrid id="DataGrid1" runat="server"></asp:DataGrid>
        </form>
    </body>
</html>

Файл с кодом:

using System;
using System.Data;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
 
public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        String strConnect;
        String strCommand;
        strConnect = @"Data Source=.\SQLExpress;Initial 
Catalog=Northwind;Integrated Security=True";
        SqlConnection myConn = new SqlConnection(strConnect);
        strCommand = "SELECT CategoryName, CategoryID FROM 
Categories";
        SqlDataAdapter myData = new SqlDataAdapter(strCommand, 
myConn);
 
        DataSet DataSet1 = new DataSet();
        myData.Fill(DataSet1, "Categories");
        strCommand = "SELECT ProductName, UnitPrice, CategoryID 
FROM Products";
        myData.SelectCommand.CommandText = strCommand;
        myData.Fill(DataSet1, "Products");
        DataSet1.Relations.Add(DataSet1.Tables[0].Columns["CategoryID"], 
DataSet1.Tables[1].Columns["CategoryID"]);
 
        DataView myView = new DataView(DataSet1.Tables["Products"], 
"", "ProductName", DataViewRowState.CurrentRows);
        DataGrid1.DataSource = myView;
        DataGrid1.DataBind();
        DropDownList1.DataSource = DataSet1.Tables[0];
        DropDownList1.DataTextField = "CategoryName";
        DropDownList1.DataValueField = "CategoryID";
        DropDownList1.DataBind();
        myConn.Close();
 
    }
}

Окно внешних источников данных

В Visual Studio 2005 существует 3 вкладки просмотра проектов: Solution Explorer, Class Explorer, Server Explorer. Первыми двумя активно пользовались все, кто писал программы на C# (или другом языке), а третий по умолчанию не виден, откройте его из меню View

—> Server Explorer. Это окно позволяет работать с соединениями баз данных, просматривать статистику работы сервера (в VWD Express нет пункта Servers).

http://www.intuit.ru/department/se/aspdotnet/6/6_1.png


Рис. 6.1. 

Соединение можно установить как с MS SQL, так и с файлом Access и любым источником ODBC, а также Oracle. Можно также создать новую базу данных MS SQL.

http://www.intuit.ru/department/se/aspdotnet/6/6_2.png


Рис. 6.2. 

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

В лекции 3 мы создали страницы с голосованиями, но никак не обрабатывали результаты. Сейчас мы будем сохранять результаты голосования.

1. Вначале надо создать базу данных. В окне Server Explorer нажмите правой клавишей мыши на пункт Data Connections, в контекстном меню выберите Create New Sql Database.

http://www.intuit.ru/department/se/aspdotnet/6/6_3.png


Рис. 6.3. 

.\SQLExpress равносильно (local) SQLExpress и означает сервер на локальной машине. Можно подключиться к серверу и по сети. Мы создали базу данных Polls, которая находится на сервере. Можно создать базу данных в отдельном файле .mdf, тогда ее можно будет переносить на другой компьютер.

2. Создание таблицы. Это можно сделать и программно, и через окно Server Explorer. Там уже появился узел созданной базы Polls.dbo. Раскройте его, правой клавишей мыши кликните на пункт Tables, в контекстном меню выберите Add New Table. Заполните следующие значения:

Column Name

Data Type

Allow Nulls

Id

int

+

variant

nvarchar(100)

voices

int

Поле id создается как уникальный идентификатор варианта, и оно должно быть первичным ключом таблицы. Кликните мышью на первой строке и выберите пункт Set Primary Key. Поле voices (количество голосов) при создании должно быть равно 0. В Columns Properties найдите строчку Default Value or Binding и впишите значение 0.

Создание той же самой таблицы в программном режиме:

    protected void Page_Load(object sender, EventArgs e)

    {

        String strCreateTable = "create table poll(id int

NOT NULL PRIMARY KEY, variant nvarchar(100), voices int DEFAULT 0)";

        CreateSqlTable(strCreateTable, "Data Source=.\\SQLEXPRESS;Initial

Catalog=Polls;Integrated Security=True");

    }

 

    public void CreateSqlTable(string SqlQuery, string myConnectionString)

    {

        SqlConnection myConnection = new SqlConnection(myConnectionString);

        SqlCommand myCommand = new SqlCommand(SqlQuery, myConnection);

        myCommand.Connection.Open();

        myCommand.ExecuteNonQuery();

        myConnection.Close();

    }

3. Заполнение таблицы вариантами. Кликните мышью на таблице и выберите пункт Show Table Data. Значения id не должны повторяться.

http://www.intuit.ru/department/se/aspdotnet/6/6_4.png


Рис. 6.4. 

4. Создание серверной процедуры. При каждом голосовании значение поля voices одной из записей таблицы, соответствующей нужному пункту, должно возрастать. Это удобнее сделать с помощью процедуры, которая принимает аргумент id и обновляет нужное поле. Кликните мышью на узел Stored Procedures и выберите пункт Add New Stored Procedure:

CREATE PROCEDURE dbo.CountVote

   (

   @choiceid int = 0  

   )

  

AS

      DECLARE @Count INT

      SELECT @Count = voices FROM poll WHERE id=@choiceid

      UPDATE poll SET voices=@Count+1  WHERE id=@choiceid

   RETURN

В этой процедуре заключены 2 взаимосвязанных команды, связанных общей логикой, а для выполнения ее понадобится только один объект Command. Также заметьте, что для ее вызова не нужно знать внутреннее устройство таблицы. Перед переменными процедуры и параметрами ставится @, чтобы отличить их от полей таблицы.

Оператор SELECT извлекает записи из таблицы poll, которые соответствуют условию после ключевого слова WHERE. Так как id — ключевое (уникальное) поле и выбирается одно поле voices, возвращается одно значение, которое можно записать в переменную. Затем в операторе UPDATE изменяется на увеличенное значение переменной @Count.

5. Привязка к данным. На новой странице создайте элементы Button и RadioButtonList (можно и CheckBoxList) перетаскиванием из Toolbox. На RadioButtonList имеется стрелка, открывающая Smart Tag. С помощью него можно сконфигурировать соединение с нужной таблицей или внести значения вручную. При нажатии на Configure Data Source появится мастер соединений. Выберите New Data Source. На втором шаге мастер предложит выбрать тип источника. Выберите Database. На следующем шаге из выпадающего списка выберите .\sqlexpress.Polls.dbo. На четвертом шаге мастер предложит сохранить строку соединения в конфигурационном файле. Сохраним, она может понадобиться. Для заполнения переключателей необходимы 2 поля: в variant содержится текст варианта, который будет виден в форме, а в id — номер варианта, который связан с DataValueField списка переключателей и будет передаваться в процедуру сервера как параметр.

http://www.intuit.ru/department/se/aspdotnet/6/6_5.png


Рис. 6.5. 

При желании на этом шаге можно отсортировать значения, например, по алфавитному порядку текстов нажатием на кнопку ORDER BY. На предпоследнем шаге можно протестировать полученный запрос, и если все в порядке, то на последнем шаге ставим variant как источник для показа и id для значений. На странице должно получиться примерно следующее:

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

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

<title>Голосование для программистов</title>

</head>

<body>

<br /><br />

Какой язык программирования Вы предпочитаете?<br />

<form runat="server" id="voting">

    <asp:RadioButtonList ID="RadioButtonList1" runat="server"

DataSourceID="SqlDataSource1" DataTextField="variant"

DataValueField="id">

    </asp:RadioButtonList>

<asp:SqlDataSource ID="SqlDataSource1" runat="server"

ConnectionString="<%$ ConnectionStrings:PollsConnectionString %>"

        SelectCommand="SELECT [id], [variant] FROM [poll] ORDER

BY [variant] "></asp:SqlDataSource>

    <br />

    <br />

    <asp:Button ID="Button1" runat="server" Text="Button" /><br />

<br />

<br /><br />

</form>

<asp:Label id="Message" runat="server" />

</body>

</html>

6. Обработка результатов. Процедуру необходимо вызвать с параметром, взятым из свойства Value группы переключателей. Свойство Parameters SqlCommand является коллекцией, в данном случае в нее надо добавить один элемент:

public partial class Voting : System.Web.UI.Page

{

    public void ExecuteStoredProcedure(string ProcedureName,

string myConnectionString, int id)

    {

        SqlConnection myConnection = new

SqlConnection(myConnectionString);

        SqlCommand myCommand = new SqlCommand(ProcedureName,

myConnection);

        myCommand.CommandType = CommandType.StoredProcedure;

        SqlParameter myParm = myCommand.Parameters.Add("@choiceid",

SqlDbType.Int, 4);

        myParm.Value = id;

        myCommand.Connection.Open();

        myCommand.ExecuteNonQuery();

 

        myConnection.Close();

    }

 

    protected void Page_Load(object sender, EventArgs e)

    {

        String strConnection = "Data Source=.\\SQLEXPRESS;Initial

Catalog=Polls;Integrated Security=True";

        if (Page.IsPostBack)

        {

            String strProc="CountVote";

            Message.Text = RadioButtonList1.SelectedValue;

            ExecuteStoredProcedure(strProc, strConnection,

Int32.Parse(RadioButtonList1.SelectedValue) );

        }

    }

}

При работе с базами данных важно соблюдать принцип: "занимай ресурсы как можно позже и освобождай как можно раньше". Поэтому соединение закрывается сразу после выполнения процедуры сервера.

Заключение

Работа с базами данных в ASP .NET — настолько обширная тема, что ее невозможно охватить в одной лекции. Хотя классы ADO .NET инкапсулированы в более удобные классы источников данных, их необходимо знать, чтобы лучше понять новую модель, принятую в ASP .NET 2.0.

Объектная модель источников данных

"Прежде всего нужны факты, а уж потом можно делать с ними, что хочешь."

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

В Visual Studio .NET 2002 и 2003 можно было создавать привязки данных к странице по технологии "drag-and-drop". Эта технология была удобна тем, что упрощала написание кода, но вместе с тем она усложняла его модификацию. Объекты данных DataAdapter и DataConnection напрямую связывались Visual Studio 2005 формой. Сейчас это тоже возможно, но технология изменилась. Введена новая объектная модель источников данных. Классы-источники данных обеспечивают лучшую абстрактизацию, чем использование классов ADO.

Один из компонентов этой модели — строка соединения с источником данных. В Visual Studio 2005 все строки добавляются в конфигурационный файл web.config:

<configuration>
  <appSettings/>
  <connectionStrings>
  <add name="DemoBaseConnectionString1" 
connectionString="Data Source= \SQLEXPRESS;Initial 
Catalog=DemoBase;Integrated Security=True"
   providerName="System.Data.SqlClient" />
  <add name="DatabaseConnectionString1" connectionString="Data 
Source=.\SQLEXPRESS;AttachDbFilename="C:\Program 
Files\Microsoft Visual Studio 8\SDK\v2.0\QuickStart\aspnet\sam-
ples\data\App_Data\Database.mdf";Integrated 
Security=True;Connect Timeout=30;User Instance=True"
   providerName="System.Data.SqlClient" />
  </connectionStrings>

Разные страницы могут использовать одну и ту же строку соединения. Если по какой-либо причине соединение нужно будет изменить, например, если сервер изменил свое местоположение, изменения придется вводить централизованно в файле web.config.

Окно Data WebMatrix позволяет соединяться только с базами Access и SQL Server. Также работает перетаскивание, но требуется, чтобы в таблице имелся первичный ключ. Он не поддерживает и представлений (View) Access.

В WebMatrix существуют собственные элементы управления с префиксом wmx — AccessDataSourceControl и SqlDataSourceControl. Строка соединения записывается в свойство ConnectionString такого элемента управления. Программа WebMatrix служила испытательным полигоном для тех новых возможностей, которые позже были добавлены в Visual Studio .NET 2005:

    <wmx:AccessDataSourceControl id="AccessDataSourceControl1" 
runat="server" ConnectionString="Provider=Microsoft.Jet.OLEDB.4.0; 
Ole DB Services=-4; Data Source=D:\My_DOCs\guestbook.mdb" 
SelectCommand="SELECT * FROM 
[guestbook]"></wmx:AccessDataSourceControl>
    <wmx:SqlDataSourceControl id="SqlDataSourceControl1" 
runat="server" ConnectionString="server='SQLEXPRESS'; 
trusted_connection=true; database='Northwind'" 
SelectCommand="SELECT * FROM [Categories]" DeleteCommand="" 
UpdateCommand=""></wmx:SqlDataSourceControl>

Итак, строка соединения состоит из указания провайдера, если это Oledb, сервера и базы на этом сервере. База может находиться в отдельном файле с расширением .mdf. При соединении через ODBC указывается имя источника данных, тип базы, путь к файлу и драйвер:

<add name="ConnectionString1" 
connectionString="DSN=BB;DBQ=D:\Programming\Brain-
bench\brainbench.mdb;DriverId=25;FIL=MS 
Access;MaxBufferSize=2048;PageTimeout=5;" 
providerName="System.Data.Odbc" />

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

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

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!Page.IsPostBack)
    {
      // Создание класса ConnectionStringSettings
      ConnectionStringSettings conn = new ConnectionStringSettings();
      conn.ConnectionString = "Server=localhost; " +
      "User ID=sa;Password=m1d2ffnkl; " +
      "Database=Northwind;Persist Security Info=True";
      conn.Name = "Northwind ConnectionString";
      conn.ProviderName = "System.Data.SqlClient";
      //Добавление строки в файл web.config
      ConfigurationManager.ConnectionStrings.Add(conn);
    }
  }

Элементы-источники данных (Data Source Controls)

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

<asp:BulletedList ID="BulletedList1" runat="server" 
BulletStyle="Square" DataTextField="CategoryName" 
DataValueField="CategoryID">
</asp:BulletedList>
 
protected void Page_Load(object sender, EventArgs e)
{
    SqlConnection conn = new SqlConnection(@"Data 
Source=(local)\sqlexpress;Initial Catalog=Northwind;Integrated 
Security=True");
    SqlCommand cmd = new SqlCommand("SELECT CategoryID, 
CategoryName FROM Categories", conn);
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);
    BulletedList1.DataSource = ds;
    BulletedList1.DataBind();
}

Теперь элементы управления связываются c элементом-источником посредством свойства DataSourceID. Любой класс-источник данных может быть связан почти с любым классом для отображения данных, и это предоставляет большую гибкость.

Всего в ASP .NET 5 элементов-источников данных: SqlDataSource, AccessDataSource и ObjectDataSource для работы с табличными источниками данных и XmlDataSource и SiteMapDataSource — для работы с иерархическими данными.

SqlDataSource позволяет соединяться с большинством реляционных СУБД. Sql в названии класса означает, что служит для соединения с базами, которые понимают язык запросов Sql, а не только с MS SQL Server.

AccessDataSource оптимизирован для работы с базами Access. Например:

<asp:AccessDataSource ID="AccessDataSource1" runat="server" 
DataFile="~/App_Data/guestbook.mdb"
    SelectCommand="SELECT [WriteDate], [UserName], [UserMail], 
[Message] FROM [guestbook]">
</asp:AccessDataSource>

SiteMapDataSource — это специализация XmlDataSource, работает с файлами навигации по сайту и служит источником данных для элементов управления навигации.

ObjectDataSource нужен для соединения с написанными программистом бизнес-объектами.

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

SqlDataSource

SqlDataSource объединяет в себе возможности SqlConnection и SqlDataAdapter (плюс дополнительные).

Итак, у нас есть строка подключения в файле web.config:

<add name="DemoBaseConnectionString1" connectionString="Data 
Source=(local)\SQLEXPRESS;Initial Catalog=DemoBase;Integrated 
Security=True"
providerName="System.Data.SqlClient" />

В свойство ConnectionString записывается эта строка:

<asp:SqlDataSource ID="SqlDataSource1" runat="server"
    ConnectionString="<%$ 
ConnectionStrings:DemoBaseConnectionString1 %>"
    ProviderName="<%$ 
ConnectionStrings:DemoBaseConnectionString1.ProviderName %>"
</asp:SqlDataSource>

В свойстве DataSourceMode SqlDataSource задается, посредством DataReader или DataSet получаются данные. При чтении посредством DataReader некоторые возможности не поддерживаются.

Получение данных связано со свойствами, похожими на свойства SqlDataAdapter: SelectCommand, SelectCommandType, DeleteCommand, DeleteCommandType и так далее. SelectCommandType может быть 2 типов — Text и StoredProcedure. Команды выполняются, когда вызываются соответствующие методы.

Метод Select вызывается с параметром типа DataSourceSelectArguments и возвращает DataSet или IDataReader в зависимости от значения свойства DataSourceMode, остальные же методы вызываются без параметров и возвращают количество обработанных строк:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT * FROM [Customers]"
 ProviderName="<%$ 
ConnectionStrings:NorthwindConnectionString.ProviderName %>">
</asp:SqlDataSource>

Этот SqlDataSource читает все записи из таблицы Customers с помощью простого запроса в DataSet:

<asp:SqlDataSource ID="SqlDataSource3" runat="server" 
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="[Ten Most Expensive Products]" 
DeleteCommandType="StoredProcedure">
</asp:SqlDataSource>

Метод Select нет необходимости вызывать явно. Он вызывается автоматически, когда связанному с SqlDataSource элементу нужны данные для отображения.

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

Значения параметров можно получать и из других разнообразных источников.

Есть несколько классов параметров — наследников класса Parameter: CookieParameter использует значение ключа файла cookie, FormParameter — переменных формы, QuerystringParameter — адресной строки, ProfileParameter — профиля пользователя и SessionParameter — переменной сессии:

<asp:Parameter Name="UID" Type="Int32" DefaultValue="0" />

Такой тип параметров применяется, если SqlDataSource используется как источник данных для элементов с автоматическим связыванием — GridView, FormView, DetailsView. Значение параметра передается во "внутренностях" этих элементов.

В других случаях задействуется ControlParameter, то есть значение параметра берется из элемента управления. Также задается свойство, откуда и берется значение. Хотя если это Text, его можно не писать:

<asp:SqlDataSource ID="SqlDataSource3" runat="server" 
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="Sales by Year" 
SelectCommandType="StoredProcedure">
    <SelectParameters>
    <asp:Parameter Name="Beginning_Date" Type="DateTime" 
DefaultValue="01.01.1998"/>
    <asp:ControlParameter Name="Ending_Date" Type="DateTime" 
ControlID="Calendar2"/>
    </SelectParameters>
</asp:SqlDataSource>

Источник параметра типа "Дата" — элемент управления "Календарь". При заданном свойстве параметра ConvertEmptyToNull текстовый параметр конвертируется в Null, если он пустой (равен System. String.Empty).

Свойство CancelSelectOnNullParameter определяет, будет ли прерван запрос, если значение какого-либо параметра равно Null:

<asp:SqlDataSource ID="SqlDataSource4" runat="server" 
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT * FROM [Customers] where @Country is 
null or Country = @Country"
 ProviderName="<%$ 
ConnectionStrings:NorthwindConnectionString.ProviderName %>" 
CancelSelectOnNullParameter="False">
    <SelectParameters> 
        <asp:QueryStringParameter Name="Country" 
QueryStringField="Country" /> 
    </SelectParameters>
</asp:SqlDataSource>

Этот запрос будет брать параметр из командной строки, например

http://localhost:3457/WebSite4/CustomersByCountry.aspx?Country=UK

Кэширование

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

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

В SqlDataSource кэширование и сортировка возможны только при получении данных через DataSet. Если DataSourceMode равно DataReader, а EnableCachingTrue, будет выброшено исключение NonSupportedException.

Длительность кэширования можно задать в свойстве CacheDuration, это может быть определенное количество секунд или Infinite, то есть данные никогда не обновляются.

Поведение кэширования зависит от сочетания свойств CacheDuration и CacheExpirationPolicy. Если значение CacheExpiration Policy равно Absolute, то элемент запрашивает информацию через промежутки времени, определенные в CacheDuration, а старую стирает из памяти. Если CacheExpirationPolicy равен значению Sliding, то SqlDataSource начинает отсчет времени после каждого запроса к нему. Данные из кэша устаревают, если в течение времени CacheDuration не было ни одного Select-запроса.

В FilterExpression задается выражение для фильтрации, причем формат этих выражений аналогичен тому, что используется для форматирования строк с параметрами в фигурных скобках {0}, {1}, в которые подставляются значения из источника, указанного в Filter Parameters:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT * FROM [Customers]" ProviderName="<%$ 
ConnectionStrings:NorthwindConnectionString.ProviderName %>"
    EnableCaching="True" CacheExpirationPolicy="Sliding">
</asp:SqlDataSource>

У этого элемента включено кэширование, и он является источником данных для GridView.

<asp:SqlDataSource ID="SqlDataSource2" runat="server" 
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT * FROM [Customers]" ProviderName="<%$ 
ConnectionStrings:NorthwindConnectionString.ProviderName %>"
FilterExpression="CustomerID=’{0}’ "
<FilterParameters>
<asp:ControlParameter Name="CustomerID" ControlId="GridView1"
PropertyName="SelectedValue"></asp:ControlParameter>
</FilterParameters>
    </asp:SqlDataSource>

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

Сортировка

В свойстве SortParameterName можно записать список полей, по которым проводится сортировка, возможно с добавлением опции Desc для сортировки в порядке убывания. Параметры передаются в команду Select, если это серверная процедура.

ObjectDataSource

Как уже было сказано, этот класс работает с бизнес-объектами. А что же это такое? Это такие классы, которые инкапсулируют логику работы с данными, нужными в приложении. Класс бизнес-объекта может быть написан на любом языке .NET. Как и все классы, он располагается в папке App_Code. ObjectDataSource работает как связующее звено между бизнес-объектами и элементами управления, отображающими данные. Получается многоуровневая компонентная архитектура. Классы бизнес-объектов могут поменять свое внутреннее представление, и это никак не отразится на страницах, которые их используют. ObjectDataSource работает во многом так же, как SqlDataSource, с той разницей, что он имеет дело не с базой данных, а с классом.

Свойство TypeName класса ObjectDataSource указывает на используемый класс. Класс бизнес-объекта должен поддерживать конструктор и 4 метода (возможно, и больше) — для чтения, редактирования, удаления и добавления данных в источник данных. Элемент управления ObjectDataSource пользуется этими методами.

Например, свойство SelectMethod указывает на метод класса бизнес-объекта, который возвращает данные.

Откуда бизнес-объект берет данные, ему не важно. Некоторые бизнес-объекты работают с базами данных, некоторые — с сессией или текстовыми файлами. Главное, что метод, который он использует для чтения, должен возвращать класс, реализующий интерфейс IEnumerable. UpdateMethod — метод, который обновляет данные. Аналогичную функцию выполняют DeleteMethod и InsertMethod.

Класс бизнес-объекта может поддерживать метод SelectCount, который возвращает общее количество объектов в источнике данных. ObjectDataSource вызывает этот метод, чтобы реализовать разбиение на страницы.

Рассмотрим это на примере:

public class Continent
{
    ArrayList ContinentArrayList;
    public Continent()
    {
        ContinentArrayList = new ArrayList();
        ContinentArrayList.Add("Worldwide");
        ContinentArrayList.Add("America");
        ContinentArrayList.Add("Africa");
        ContinentArrayList. Add("Asia-Pacific");
 
    }
    public ArrayList List()
    {
        return ContinentArrayList;
    }
    public int SelectCount()
    {
        return ContinentArrayList.Length;
    }
}

Даже такой примитивный класс может использоваться как источник данных для ObjectDataSource, так как ArrayList реализует IEnumerable. Вместо свойств *Command ObjectDataSource использует *Method:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
     SelectMethod="List" TypeName="Continent">
</asp:ObjectDataSource>
<asp:RadioButtonList ID="RadioButtonList1" runat="server" 
    DataSourceID="ObjectDataSource1">
</asp:RadioButtonList></div>

Достигается тот же эффект, что и раньше, когда данные вставлялись на странице или в классе страницы, но теперь получение данных инкапсулировано в классе Continent. Класс может изменить способ получения данных, не меняя интерфейса. Чаще всего данные все-таки получают из баз данных, XML-файлов или web-сервисов. Классы бизнес-логики могут разрабатывать одни члены команды, а заниматься дизайном страниц — другие. Их можно использовать и в обычных приложениях в Windows Forms.

ObjectDataSource может работать и с типизированными наборами данных, которые можно создать с помощью мастера. Попробуем это сделать на примере таблицы Customers. Создайте в папке App_Code новый файл и в диалоге выбора типа файла выберите dataset. Назовите его Customers. Мастер предложит выбрать строку соединения. Выберите NorthWindConnectionString (если его нет в проекте, создайте, как показано в предыдущей лекции). На следующем шаге мастер предложит выбрать из трех вариантов: использование запросов SQL, создание хранимых процедур или использование готовых процедур. Выберите второе, так как готовых процедур, которые бы обновляли данные, в базе Northwind нет. На следующем шаге нужно будет создать процедуры, это можно сделать с помощью QueryBuilder, очень похожем на дизайнер запросов в MS Access. В списке таблиц выберите Customers, а в таблице несколько полей. Должен получиться запрос

SELECT CustomerID, CompanyName, ContactName, ContactTitle, Country, City FROM Customers

После этого закройте QueryBuilder и нажмите на кнопку Advanced Options.

http://www.intuit.ru/department/se/aspdotnet/7/7_1.png


Рис. 7.1. 

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

В результате получится файл Customers.xsd, по формату — файл схемы XML (XML Schema Definition), в котором описано и создание процедур, и команды для работы с базой вместе с параметрами, и еще один маленький файл Customers.xss. После этого проект желательно скомпилировать.

Мы получили компонент данных. Все готово для связывания его с ObjectDataSource. Перетащите значок нужного класса на форму и с помощью SmartTag запустите еще один мастер. На первом шаге настройте его на CustomersDataAdapters.CustomersDataAdapter. На втором надо выбрать подходящие функции для команд Select, Update, Delete, Insert. Вариантов будет немного — после выбора нажмите Finish. Можно привязывать наш ObjectDataSource к любому подходящему элементу управления, например, GridView:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"

SelectMethod="GetData"

TypeName="CustomersTableAdapters.CustomersTableAdapter"

DeleteMethod="Delete" InsertMethod="Insert"

OldValuesParameterFormatString="original_{0}"

UpdateMethod="Update">

    <DeleteParameters>

        <asp:Parameter Name="Original_CustomerID" Type="String" />

    </DeleteParameters>

    <UpdateParameters>

        <asp:Parameter Name="CustomerID" Type="String" />

        <asp:Parameter Name="CompanyName" Type="String" />

        <asp:Parameter Name="ContactName" Type="String" />

        <asp:Parameter Name="ContactTitle" Type="String" />

        <asp:Parameter Name="Country" Type="String" />

        <asp:Parameter Name="City" Type="String" />

        <asp:Parameter Name="Original_CustomerID" Type="String" />

    </UpdateParameters>

    <InsertParameters>

        <asp:Parameter Name="CustomerID" Type="String" />

        <asp:Parameter Name="CompanyName" Type="String" />

        <asp:Parameter Name="ContactName" Type="String" />

        <asp:Parameter Name="ContactTitle" Type="String" />

        <asp:Parameter Name="Country" Type="String" />

        <asp:Parameter Name="City" Type="String" />

    </InsertParameters>

</asp:ObjectDataSource>

    <asp:GridView ID="GridView1" runat="server" AllowPaging="True"

AllowSorting="True"

    AutoGenerateColumns="False" DataKeyNames="CustomerID"

DataSourceID="1">

    <Columns>

        <asp:CommandField ShowDeleteButton="True"

ShowEditButton="True" ShowSelectButton="True" />

        <asp:BoundField DataField="CustomerID"

HeaderText="CustomerID" ReadOnly="True" SortExpression="CustomerID" />

        <asp:BoundField DataField="CompanyName"

HeaderText="CompanyName" SortExpression="CompanyName" />

        <asp:BoundField DataField="ContactName"

HeaderText="ContactName" SortExpression="ContactName" />

        <asp:BoundField DataField="ContactTitle"

HeaderText="ContactTitle" SortExpression="ContactTitle" />

        <asp:BoundField DataField="Country" HeaderText="Country"

SortExpression="Country" />

        <asp:BoundField DataField="City" HeaderText="City"

SortExpression="City" />

    </Columns>

</asp:GridView>

Класс бизнес-объекта создается неявно. Из файла .xsd можно получить класс типизированного набора данных на языке C# с помощью утилиты xsd.exe.

xsd.exe /dataset /language:CS Customers.xsd.

Из одного класса могут получать данные разные элементы Object DataSource. В приложении Personal Starter Kit определен класс Photo Manager, который работает с базой данных Personal.mdf:

  public static Stream GetPhoto(int photoid, PhotoSize size)

  {

    using (SqlConnection connection = new

SqlConnection(ConfigurationManager.ConnectionStrings["Personal"].C

onnectionString)) {

      using (SqlCommand command = new SqlCommand("GetPhoto", con-

nection)) {

        command.CommandType = CommandType.StoredProcedure;

        command.Parameters.Add(new SqlParameter("@PhotoID",

photoid));

        command.Parameters.Add(new SqlParameter("@Size",

(int)size));

        bool filter =

!(HttpContext.Current.User.IsInRole("Friends") ||

HttpContext.Current.User.IsInRole("Administrators"));

        command.Parameters.Add(new SqlParameter("@IsPublic",

filter));

        connection.Open();

        object result = command.ExecuteScalar();

        try {

          return new MemoryStream((byte[])result);

        } catch {

          return null;

        }

      }

    }

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

Заключение

Мы рассмотрели классы-элементы управления, которые отвечают за получение данных. Эти классы предназначены в первую очередь для облегчения труда программиста (по сравнению с предыдущими версиями). Наиболее простые страницы с помощью этих элементов создаются даже без написания программного кода. В следующих двух лекциях подробнее займемся отображением данных. XMLDataSource будет рассмотрен в лекции 10, а SiteMapSource — в лекции 11.

Элементы-потребители данных

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

Элементы, которые могут быть связаны с элементами-источниками данных, многообразны. Во-первых, это уже хорошо знакомые DropDownList, ListBox, CheckBoxList, RadioButtonList, BulletedList. Однако у всех них необходимо в качестве источника данных указывать не DataSource, а DataSourceID. Все эти элементы отображать могут только одно поле, указанное в DataTextField, с возможностью задания второго в качестве индексного в свойстве DataValueField:

<asp:SqlDataSource ID="SqlDataSource3" runat="server" 
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT [CategoryName], [CategoryID] FROM 
[Categories]">
</asp:SqlDataSource>
<asp:CheckBoxList ID="CheckBoxList1" runat="server" 
AppendDataBoundItems="True" DataSourceID="SqlDataSource3"
    DataTextField="CategoryName" DataValueField="CategoryID">
</asp:CheckBoxList>
<asp:DropDownList ID="DropDownList1" runat="server" 
DataSourceID="SqlDataSource3" DataTextField="CategoryName">
</asp:DropDownList>
<asp:BulletedList ID="BulletedList1" runat="server" 
BulletStyle="UpperRoman" DataSourceID="SqlDataSource3"
    DataTextField="CategoryName" DataValueField="CategoryName">
</asp:BulletedList>

AppendDataBoundItems — это новое свойство. Оно позволяет комбинировать данные из элемента-источника с данными, статически объявленными на странице.

Очень интересны элементы управления Repeater и DataList. Они позволяют управлять отображением данных с помощью шаблонов.

Синтаксис динамического связывания

Можно получать данные, связанные с элементом управления, в его декларации на странице. Это делается с помощью разновидности блока отображения. В ранних версиях ASP .NET с помощью такого механизма можно было только читать, но теперь возможно и двустороннее связывание. Особенно это важно в элементах управления, использующих шаблоны. Хотя DataGrid и GridView автоматически отображают данные, но и в них для создания нужных эффектов используются столбцы-шаблоны.

Для привязки к данным используются разделители <%# %>. В жизненном цикле каждого элемента управления наступает событие DataBinding, во время которого и вычисляются все выражения, заключенные в этот тег.

Если в функции Page_Load мы писали

     ContinentDropDownList.DataSource = ContinentArrayList;

то на странице это можно сделать с помощью

<asp: DropDownList id=" ContinentDropDownList " datasource='<%# 
ContinentArrayList %>' runat="server">

В ASP .NET 1.x данные читались в объекты DataSet или DataReader, после чего вызывался DataBind. В ASP .NET 2.0, если же установлено свойство DataSourceID, то событие DataBinding вызывается автоматически.

В сгенерированном ASP .NET классе страницы в обработчике этого события для ContinentDropDownList будет выполняться код

datasource = ContinentArrayList;

При этом в локальном кэше создается копия прочтенных данных. Для элементов Repeater, DataList, DataGrid синтаксис привязки данных разбирается в событии ItemDataBound, для GridView — в RowDataBound. Эти события вызываются столько раз, сколько записей в источнике данных. Каждый раз, когда ASP .NET встречает эти разделители в шаблоне, внутри элемента Item (RepeaterItem, DataListItem, DataGridItem) создается элемент типа DataBoundLiteralControl, внутри которого записывается выражение внутри разделителей. В обработчике события DataBinding этого элемента определена переменная Container, которая указывает на этот самый Item, то есть секцию элемента. Классы Item хранят данные в свойстве DataItem. Поэтому в шаблонах доступ к данным происходит с помощью синтаксиса Container.DataItem.

Возможный синтаксис для доступа к полю:

<%# ((System.Data.DataRowView)Container.DataItem)["City"]%>

в случае, когда данные получены из DataReader,

<%# ((System.Data.IDataRecord)Container.DataItem)["City"]%>

в случае, когда данные получены из DataSet,

или

<%# DataBinder.Eval(Container.DataItem, "City") %>

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

DataBinder.Eval(Container.DataItem, "Age","{0:2d}")

В ASP .NET 2.0 синтаксис можно упростить и написать

<%# Eval("City")%>

В новых элементах управления GridView, DetailsView, FormView, где поддерживается двунаправленный вывод данных, можно вызывать метод Bind. Его используют в шаблонах редактируемых строк:

<asp:TextBox ID="EditFirstNameTextBox" Runat="Server" Text='<%# 
Bind("FirstName") %>' />

Repeater

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

Шаблон — это множество тегов HTML и серверных элементов управления, которые задают образец для отображения составной части сложного элемента управления. DataGrid может использовать шаблоны или нет, но Repeater без них существовать не может — сам по себе он не имеет визуального представления. Таким образом, программист сам определяет его внешний вид. Кроме DataSourceID и DataMember, собственных свойств у него нет. Поэтому у программиста есть полный контроль над тем, как выводится Repeater.

Как минимум, должен быть описан шаблон ItemTemplate. HeaderTemplate отображается один раз в начале отрисовки репитера, FooterTemplate в конце, SeparatorTemplate между отображением каждого пункта, AlternatingItemTemplate — для четных пунктов. Все серверные элементы управления в шаблон помещаются целиком, поэтому, чтобы получить таблицу, используют простые теги HTML. Например, открывающий тег <table> помещают в HeaderTemplate, а закрывающий — в FooterTemplate.

В этом примере составляются характеристики сотрудников:

<asp:Repeater ID="Repeater1" runat="server"

DataSourceID="SqlDataSource1" >

    <HeaderTemplate><asp:Label runat="server" ID="Header1"

        BackColor="DarkOrange" ForeColor="ActiveCaptionText"

        Font-Bold="true" Font-Underline="true" Font-Size="Large">

        We present our valued Employees!</asp:Label>

        <table>

    </HeaderTemplate>

    <ItemTemplate>

        <tr>

         <td>

           <asp:Panel ID="Panel1" runat="server"

BackColor="LightPink">

    <asp:Image ID="Photo" runat="server" ImageUrl=<%#

Eval("PhotoPath")%>/>

               <%# Eval( "TitleOfCourtesy") %>

               <%# DataBinder.Eval(Container.DataItem,

"FirstName") %>

               <%# DataBinder.Eval(Container.DataItem,

"LastName") %>

               was born in <%# Eval("BirthDate",

"{0:dd.MM.yyyy}") %>.<p>

               <%# Eval("TitleOfCourtesy").ToString() == "Mr."

|| Eval("TitleOfCourtesy").ToString() == "Dr." ? "He" : "She"%>

               lives in beautiful city

               <%#

((System.Data.DataRowView)Container.DataItem)["City"]%>,

               <%# Eval( "Region") %> in

               <%# DataBinder.Eval(Container.DataItem,

"Country") %>.</p>

               <p>We appreciate <%#

Eval("TitleOfCourtesy").ToString() == "Mr." ||

Eval("TitleOfCourtesy").ToString() == "Dr." ? "his" : "her"%>

work as <%# Eval("Title") %>.</p>

               <p><%# DataBinder.Eval(Container.DataItem,

"Notes") %></p>

            </asp:Panel>

         </td>

       </tr>

    </ItemTemplate>

    <FooterTemplate>

    </table></FooterTemplate>

</asp:Repeater>

<asp:SqlDataSource ID="SqlDataSource1" runat="server"

        ConnectionString="<%$

ConnectionStrings:NorthwindConnectionString1 %>"

        SelectCommand="SELECT * FROM [Employees]"

></asp:SqlDataSource>

В браузере это выглядит так:

http://www.intuit.ru/department/se/aspdotnet/8/8_1.png


Рис. 8.1. 

Обратите внимание на условные выражения:

<%# Eval("TitleOfCourtesy").ToString() == "Mr." ||

Eval("TitleOfCourtesy").ToString() == "Dr." ? "He" : "She"%>

Благодаря этому в предложении о мужчине употребляются местоимения мужского, а в предложении о женщине — женского рода. В таблице нет поля для пола, но его можно вычислить по полю TitleOfCourtesy, который у мужчин бывает или "Mr. ", или "Dr.".

Если нужно изменить внешний вид всего шаблона, проще всего поместить все его содержимое или весь элемент в Panel и определить в нем стили.

С помощью элемента управления Repeater можно создавать этикетки для конвертов, пригласительные билеты и так далее.

DataList

DataList имеет те же черты, что и Repeater, то есть выводит данные согласно шаблонам. Однако это более богатый элемент управления. Во-первых, он поддерживает выбор, редактирование, удаление и вставку. Поэтому список шаблонов пополнился SelectedItemTemplate и EditItemTemplate. Кроме того, у него есть верхний и нижний колонтитулы со стилями HeaderStyle и FooterStyle.

Во-вторых, можно изменить способы отображения. По умолчанию DataList выводит данные поколонно в таблице. Свойство RepeatLayout, установленное как Flow, убирает табличные теги из выходного потока. RepeatDirection меняет направление вывода с вертикального на горизонтальное. RepeatColumns задает количество столбцов таблицы, по умолчанию равное 1.

DataList — наследник абстрактного класса BaseDataList, который наследует WebControl. Поэтому у него, в отличие от Repeater, имеются визуальные свойства. При отображении он представляет собой таблицу, поэтому присутствуют свойства CellPadding и CellSpacing.

У DataList есть шаблон по умолчанию, Visual Studio 2005 и VWD создают его в виде вертикально расположенных меток для каждого поля, а слева от них помещают текст с названием поля. Чтобы войти в режим редактирования шаблона, нужно воспользоваться возможностью SmartTagEdit Templates. После того, как редактирование окончено, не забудьте выйти из режима — End Template Editing.

Можно спроектировать этот элемент так, чтобы в обычном состоянии отображалась краткая информация, а в выбранном состоянии — более подробная.

Посмотрим пример из Quickstarts:

    <asp:DataList id="DataList1" runat="server"
        BorderColor="black" BorderWidth="1"
        GridLines="Both" CellPadding="3"
        Font-Names="Verdana" Font-Size="8pt"
        Width="150px"
        HeaderStyle-BackColor="#aaaadd"
        AlternatingItemStyle-BackColor="Gainsboro"
        SelectedItemStyle-BackColor="yellow"
        OnItemCommand="DataList_ItemCommand"
     >
    <HeaderTemplate>
         Items
    </HeaderTemplate>
    <ItemTemplate>
        <asp:LinkButton id="button1" runat="server" Text="Show 
details" CommandName="select" />
        <%# DataBinder.Eval(Container.DataItem, "StringValue") %>
    </ItemTemplate>
    <SelectedItemTemplate>
      Item:
        <%# DataBinder.Eval(Container.DataItem, "StringValue") %>
        <br>
      Order Date:
        <%# DataBinder.Eval(Container.DataItem, "DateTimeValue", 
"{0:d}") %>
        <br>
      Quantity:
        <%# DataBinder.Eval(Container.DataItem, "IntegerValue", 
"{0:N1}") %>
      <br>
    </SelectedItemTemplate>
</asp:DataList>

А обработчик выбора записи такой:

    void DataList_ItemCommand(object Sender, 
DataListCommandEventArgs e) {
        string cmd = ((LinkButton)e.CommandSource).CommandName;
        if (cmd == "select")
            DataList1.SelectedIndex = e.Item.ItemIndex;
        BindList();
    }

Чтобы реализовать редактирование, тоже нужно обрабатывать событие. Поэтому в ASP .NET 2.0 DataList лучше применять для показа данных без редактирования, а если редактирование все же требуется — использовать элемент управления FormView.

Свойство DataKeyField имеется и у DataGrid, и у DataList. С помощью него происходит связывание с ключевым полем таблицы данных.

DataGrid

Это очень популярный элемент управления, и неудивительно. Особенно много он применялся в ASP .NET 1.x, но теперь его функции перекрываются GridView. Тем не менее его стоит изучить, так как многие его свойства схожи со свойствами GridView. DataGrid делает очень легким представление табличной информации, которая содержится в базах данных, файлах XML или создается вручную. Достаточно создать DataGrid, установить свойство DataSource и получить готовую таблицу на странице. Формат таблицы можно менять независимо от данных. Данные можно сортировать, выбирать, редактировать.

В простейшем варианте нужно установить только свойство DataSource, его значением может быть объект, реализующий интерфейс IEnumerable, например SqlDataReader, DataTable. При этом на странице выводится таблица, где строкам соответствуют записи, а столбцам — поля.

Создадим простой XML-файл с табличной информацией. Это будут данные о лауреатах Нобелевской премии по литературе и физике. Назовите ее nobel.xml:

<?xml version="1.0" encoding="utf-8" ?>
<nobel>
  <phisics>
    <phisisist>
      <name>Basov</name>
      <nationality>Russia(USSR)</nationality>
    </phisisist>
    <phisisist>
      <name>Rentgen</name>
      <nationality>Germany</nationality>
    </phisisist>
    <phisisist>
      <name>Bor</name>
      <nationality> Denmark</nationality>
    </phisisist>
  </phisics>
  <literature>
    <writer>
      <name>Boris Pasternak</name>
      <nationality>Russia</nationality>
      <work>"Doctor Zhivago"</work>
      <winningdate>1958</winningdate>
    </writer>
    <writer>
      <name>Romain Rollan</name>
      <nationality>France</nationality>
      <work>"Jean-Cristophe"</work>
      <winningdate>1915</winningdate>
    </writer>
    <writer>
      <name>Gabriel Garsia Marquez </name>
      <nationality>Columbia</nationality>
      <work>"100 years of solitude"</work>
      <winningdate>1982</winningdate>
    </writer>
    <writer>
      <name>George Bernard Shaw</name>
      <nationality>Great Britain</nationality>
      <work></work>
      <winningdate>1925</winningdate>
    </writer>
  </literature>
</nobel>

Тут построена трехуровневая иерархия. Узел <nobel> должен быть прочитан в DataSet. Внутри него есть 2 узла: один с данными о физике, второй — о литературе. Каждый из них будет помещен в DataTable. Узлы <name>, <nationality>, <work>, <winningdate> вложены в <literature> и повторяются для каждого писателя. Они будут считаны в DataColumns таблицы.

Почему узел <work></work> у Шоу пустой? Как считал сам Шоу, Нобелевскую премию 1925 года ему дали за то, что в этом году он ничего не написал.

Через методы ReadXml, WriteXml DataSet может читать данные из XML-файла.

Форма, которая читает информацию из этого файла:

<%@ Page Language="C#" Debug="true" %>

 

<%@ Import Namespace="System.Data" %>

<%@ Import Namespace="System.Xml" %>

 

<script runat="server">

 

    void binData()

    {

        String xmlFilename = Server.MapPath("") + "\\nobel.xml";

        DataSet newDataSet = new DataSet();

        newDataSet.ReadXml(xmlFilename);

        DataTable newDataTable = newDataSet.Tables[1];

        DataGrid1.DataSource = newDataTable;

        DataGrid1.DataBind();

    }

 

    void Page_Load()

    {

        if (!IsPostBack)

            bindData();

    }

</script>

 

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Data Grid Control example</title>

</head>

<body>

    <form runat="server">

        <asp:DataGrid id="DataGrid1" runat="server"

CellSpacing="10"

        BorderWidth ="0"  BackColor="AliceBlue" EditItemIndex =

"1">

        <HeaderStyle BackColor="#AAAADD" Font-Size="20pt" Font-

Bold = "True">

        </HeaderStyle>

        <AlternatingItemStyle BackColor="#80FF80" />

    </form>

</body>

</html>

Поменяв индекс в DataTable newDataTable = newDataSet.Tables[1] на 3, получим страницу с другими данными — лауреатов премии по литературе.

По умолчанию элемент DataGrid сам определяет количество полей в источнике данных и генерирует колонки таблицы. Это определяется свойством AutoGenerateColumns. С элементом управления DataGrid могут быть связаны не все типы данных. Поддерживаются примитивные типы, строки, DataTime и Decimal. Если в поле неподдерживаемый тип, столбец не будет создан. Если ни одного подходящего поля нет, будет выброшено исключение.

DataGrid имеет заголовок (Header), который по умолчанию виден, и нижний колонтитул (Footer). При автоматической генерации в заголовке каждого столбца выводится название поля.

Если AutoGenerateColumns установить в False, можно самим управлять колонками и определять более сложный его вид. В таком случае надо включать в DataGrid элементы BoundColumn. Некоторые свойства BoundColumn:

В заголовке и нижнем колонтитуле можно установить любой текст, а в заголовке — еще и картинку (HeaderText, FooterText, HeaderImageUrl).

В ячейку генерируемой DataGrid таблицы вставляется LiteralControl, текст которого берется из источника данных и форматируется в соответствии с DataFormatString. Для редактируемой строки в ячейке появляется TextBox.

Есть и другие типы колонок.

ButtonColumn отображает в каждой строке командную кнопку. Если связать ее с полем, на кнопках будут надписи из этого поля.

EditCommandColumn показывает кнопки для редактирования.

HyperLinkColumn превращает текст в гиперссылки. Например, поле PhotoPath можно показать в такой колонке, и тогда щелчок по ссылке покажет фотографию.

TemplateColumn позволяет определить шаблон отображения, как в DataList.

При желании можно программно скрывать и показывать колонки, например:

DataGrid1.Columns[1].Visible = !(DataGrid1.Columns[1].Visible);

У элемента DataGrid есть 7 свойств, задающих стили различных его частей или типов строк. Все они имеют тип TableItemStyle. Это AlternatingItemStyle, EditItemStyle, FooterStyle, HeaderStyle, ItemStyle, PagerStyle и SelectedItemStyle. Стили образуют иерархию, то есть атрибут "Стиль", который выше в иерархии, наследует те, которые ниже, если он его не переопределяет. Порядок в ней такой:

  1. EditItemStyle — стиль редактируемой строки;
  2. SelectedItemStyle — стиль выбранной строки;
  3. AlternatingItemStyle — стиль каждой второй строки;
  4. ItemStyle — стиль строки по умолчанию;
  5. ControlStyle — все свойства, которые влияют на внешний вид элемента, например BackColor. PagerStyle, FooterStyle, HeaderStyle тоже его наследуют.
  6. PagerStyle — стиль пейджера, то есть номеров страниц-гиперссылок, при выборе которых таблица перелистывается. Чтобы пейджер появился, должен быть установлен атрибут AllowPaging и количество записей должно быть больше PageSize. Все эти свойства удобно устанавливать с помощью PropertyBuilder.

В Visual Studio 2005 есть возможность автоформатирования, как и у DataList.

Новый вариант, без автоматической генерации колонок и со стилями:

<asp:DataGrid ID="DataGrid2" runat="server" BackColor="#FFE0C0"

ShowFooter="True" AutoGenerateColumns="False" PageSize="3">

    <AlternatingItemStyle BackColor="#C0FFC0" />

    <ItemStyle BackColor="#FFFFC0" />

    <EditItemStyle BackColor="#C0C000" Font-Size="XX-Large" />

    <Columns>

        <asp:BoundColumn DataField="name" FooterText="Name"

HeaderText="Фамилия"></asp:BoundColumn>

        <asp:BoundColumn DataField="nationality"

FooterText="Country" HeaderText="Страна"></asp:BoundColumn>

        <asp:BoundColumn DataField="winningdate" FooterText="Year

won" HeaderText="Год"></asp:BoundColumn>

        <asp:BoundColumn DataField="work" FooterText="Work"

HeaderText="Произведение"></asp:BoundColumn>

        </Columns>

        <FooterStyle Font-Bold="True" Font-Italic="False" Font-

Overline="False" Font-Strikeout="False" Font-Underline="False" />

        <HeaderStyle Font-Bold="True" Font-Italic="False" Font-

Overline="False" Font-Strikeout="False" Font-Underline="False" />

</asp:DataGrid>

Добавим в эту форму возможность сортировки по столбцам. DataGrid поддерживает свойство AllowSorting. Но это только потенциальная возможность сортировки, так как сам элемент сортировать не может, это должен обеспечить программист. При AllowSorting = True в заголовке выводятся гиперссылки, при нажатии на которые вызывается событие SortCommand. Так как нет автогенерации, в описание BoundColumn нужно вставить SortExpression:

    <asp:BoundColumn DataField="name" FooterText="Name"

HeaderText="Фамилия" SortExpression="name">

    </asp:BoundColumn>

Метод-обработчик события SortCommand принимает параметр типа DataGridSortCommandEventArgs, в свойстве SortExpression которого содержится строка — выражение сортировки SortExpression. Необходимо использовать это выражение для сортировки данных, полученных из источника данных

protected void DataGrid2_SortCommand(object source, DataGridSortCommandEventArgs e)

{

    ViewState["sort"] = e.SortExpression;

    bindData();

}

где bindData() вынесен в отдельную функцию и вызывается также из Page_Load:

private void bindData()

{

    String xmlFilename = Server.MapPath("") + "\\nobel.xml";

    DataSet newDataSet = new DataSet();

    newDataSet.ReadXml(xmlFilename);

    if (ViewState["sort"] != null)

        newDataSet.Tables[3].DefaultView.Sort =

(string)ViewState["sort"];

    DataTable newDataTable = newDataSet.Tables[3];

    DataGrid2.DataSource = newDataTable;

    DataGrid2.DataBind();

}

 

void Page_Load()

{

    if (!IsPostBack)

        bindData();

}

Фамилия

Страна

Год

Произведение

Romain Rollan

France

1915

"Jean-Cristophe"

George Bernard Shaw

Great Britain

1925

Boris Pasternak

Russia

1958

"Doctor Zhivago"

Gabriel Garsia Marquez

Columbia

1982

"100 years of solitude"

Name

Country

Year won

Work

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

Покажем возможность удаления, обновления и редактирования данных в DataGrid с помощью SqlDataSource.

Создайте на сервере SQL в базе DemoBase таблицу Users с тремя полями:

Column Name

Data Type

Allow Nulls

UID

int

Name

varchar(50)

Comments

varchar(250)

+

IsRegistered

bit

+

Поле UID — автоинкрементное. Поэтому операция INSERT не будет требовать задания его значения. Конечно, это первичный ключ. В таблице свойств найдите IdentitySpecification, раскройте его и выберите (IsIdentity).

Будем работать с таблицей с помощью трех процедур.

CREATE PROCEDURE dbo.SelectUsers

(

   @Col INT = 0

)

AS

IF @Col = 0

      SELECT * FROM Users

   ELSE IF @Col = 1

      SELECT * FROM Users ORDER BY NAME

   RETURN

Процедура EditUser будет использоваться для вставки записей, если @UID, и для обновления в противном случае:

CREATE PROCEDURE dbo.EditUser

   (

   @UID int = 0,

   @Name varchar(50),

   @Comments varchar(250),

   @Registered bit

)

AS

   IF @UID = 0

      INSERT INTO Users(Name, Comments, IsRegistered)

VALUES(@Name, @Comments, @Registered)

   ELSE

      UPDATE Users SET Name = @Name, Comments = @Comments,

IsRegistered = @Registered WHERE UID = @UID

   RETURN

Процедура для удаления записей.

CREATE PROCEDURE dbo.DeleteUser

(

   @UID int

)

      AS

      DELETE FROM Users WHERE UID = @UID

   RETURN

На форме будет находиться, кроме DataGrid, два элемента редактирования — NameTextBox, а также CommentTextBox и кнопка Add. Для наших тайных целей добавим элемент управления типа HiddenField. Эти цели — хранить id текущего элемента и передавать его SqlDataSource.

Добавим на форму следующий источник данных:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
    ConnectionString="<%$ 
ConnectionStrings:DemoBaseConnectionString %>"
    SelectCommand="dbo.SelectUsers" 
SelectCommandType="StoredProcedure" 
    DeleteCommand="dbo.DeleteUser"  
DeleteCommandType="StoredProcedure" 
    UpdateCommand="dbo.EditUser" 
UpdateCommandType="StoredProcedure" 
    InsertCommand="dbo.EditUser" 
InsertCommandType="StoredProcedure"
    OldValuesParameterFormatString="">
    <InsertParameters>
        <asp:Parameter Direction="ReturnValue" Name="RETURN_VALUE" 
            Type="Int32" />
        <asp:Parameter Name="UID" Type="Int32" DefaultValue="0" />
        <asp:ControlParameter Name="Name" Type="String" 
ControlID="NameTextBox"/>
        <asp:ControlParameter Name="Comments" Type="String" 
ControlID="CommentTextBox" PropertyName="Text"/>
        <asp:ControlParameter Name="Registered"  Type="Boolean" 
ControlID="Registered" PropertyName="Checked"/>
    </InsertParameters>
    <UpdateParameters>
        <asp:Parameter Direction="ReturnValue" 
Name="RETURN_VALUE"
          Type="Int32" />
        <asp:ControlParameter Name="UID" Type="Int32" 
ControlID="HiddenField1" />
        <asp:ControlParameter Name="Name" Type="String" 
ControlID="NameTextBox"/>
        <asp:ControlParameter Name="Comments" Type="String" 
ControlID="CommentTextBox"/>
        <asp:ControlParameter Name="Registered"  Type="Boolean" 
ControlID="Registered" PropertyName="Checked"/>
    </UpdateParameters>
    <DeleteParameters> 
        <asp:ControlParameter ControlID="HiddenField1" 
PropertyName="Value" Name="UID" Type="Int32" /> 
    </DeleteParameters> 
</asp:SqlDataSource>

Обратите внимание на то, что в InsertParameters значение параметра UID по умолчанию 0 и он не связан с элементом управления. В остальных случаях он связан с HiddenField1. Значение в это поле будет передаваться в обработчиках.

На этот раз DataGrid будет содержать шаблонизированные столбцы TemplateColumn. Этот тип столбца DataGrid позволяет полностью управлять форматом отображения и редактирования данных — можно выводить данные в несколько строк или использовать для редактирования данных любые элементы управления. Например, для отображения булевской информации используем элементы CheckBox:

<asp:DataGrid id="UsersDataGrid"  runat="server" 
    AutoGenerateColumns="False" DataKeyField="UID" 
    OnDeleteCommand="UsersDataGrid_DeleteCommand" 
    OnUpdateCommand="UsersDataGrid_UpdateCommand" 
    OnEditCommand="UsersDataGrid_EditCommand" 
DataSourceID="SqlDataSource1"
    AllowSorting="True" Caption="Users" CaptionAlign="Top"> 
    <Columns>
    <asp:TemplateColumn HeaderText= "Имя"> 
      <ItemTemplate> 
        <asp:Label id="Label1" runat="server" 
        Text='<%#DataBinder.Eval(Container, "DataItem.Name")%>'>
        </asp:Label>
      </ItemTemplate> 
      <EditItemTemplate> 
        <asp:TextBox id=NameTextBox runat="server" 
        Text='<%# DataBinder.Eval(Container, "DataItem.Name") %>'>
        </asp:TextBox>
      </EditItemTemplate>
    </asp:TemplateColumn> 
    <asp:TemplateColumn HeaderText="Комментарии">
      <ItemTemplate> 
        <asp:Label id="Label2" runat="server" Text='<%# 
DataBinder.Eval(Container, "DataItem.Comments")%>'></asp:Label>
      </ItemTemplate>
      <EditItemTemplate>
        <asp:TextBox id= CommentTextBox runat="server" Text='<%# 
DataBinder.Eval(Container, "DataItem.Comments")%>'>
        </asp:TextBox>
      </EditItemTemplate>
    </asp:TemplateColumn>
    <asp:TemplateColumn HeaderText= "Зарегистрирован">
      <ItemTemplate>
        <asp:CheckBox  runat="server" ID="Registered" 
Checked='<%# (bool) DataBinder.Eval(Container, 
"DataItem.isRegistered") %>'
              Enabled="False"></asp:CheckBox >
      </ItemTemplate>
      <EditItemTemplate>
        <asp:CheckBox runat="server" ID="Registered" Checked='<%# 
(bool) DataBinder.Eval(Container, "DataItem.isRegistered") 
%>'></asp:CheckBox>
      </EditItemTemplate>
      </asp:TemplateColumn>
    <asp:TemplateColumn>
      <ItemTemplate>
        <asp:LinkButton id="LinkButton1" runat="server" 
CommandName="edit">редактировать</asp:LinkButton> ::
        <asp:LinkButton id="LinkButton2" runat="server" 
CommandName="delete">удалить</asp:LinkButton>
      </ItemTemplate>
      <EditItemTemplate>
        <asp:LinkButton id="LinkButton3" runat="server" 
CommandName="update">принять</asp:LinkButton> ::
        <asp:LinkButton id="LinkButton4" runat="server" 
CommandName="cancel">отменить</asp:LinkButton>
      </EditItemTemplate>
    </asp:TemplateColumn>
  </Columns>
</asp:DataGrid>

И остальные элементы:

<asp:TextBox ID="NameTextBox" runat="server" ></asp:TextBox>
<asp:TextBox ID="CommentTextBox" 
runat="server"></asp:TextBox>
<asp:CheckBox ID="Registered" runat="server" /><br /><br />
<asp:Button ID="Button1" runat="server" Text="Add" 
OnClick="Add_Click" />
<asp:HiddenField ID="HiddenField1" runat="server" />

DataGrid уже будет выводить данные, имеет гиперссылки для правки и удаления, но при нажатии ничего не происходит. Остается написать нужный код. SqlDataSource уже знает параметры команды Delete, это единственный параметр, и связан он был с HiddenField1. Нужно записать значение ключа id в это поле:

protected void UsersDataGrid_DeleteCommand(object source, 
System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
    HiddenField1.Value = 
UsersDataGrid.DataKeys[e.Item.ItemIndex].ToString();
    // Удаление
    SqlDataSource1.Delete();
    // Обновление данных после удаления
    UsersDataGrid.DataBind();
}

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

protected void Add_Click(object sender, EventArgs e)
{
    SqlDataSource1.Insert();
    UsersDataGrid.DataBind();
}

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

protected void UsersDataGrid_EditCommand(object source, 
DataGridCommandEventArgs e)
{
    UsersDataGrid.EditItemIndex = e.Item.ItemIndex;
    NameTextBox.Visible = false;
    CommentTextBox.Visible = false;
    Registered.Visible = false;
    UsersDataGrid.DataBind();
}

Редактирование — сложнее. В шаблоне столбцов элемента DataGrid указаны текстовые поля, которые появляются после нажатия на ссылку "Редактировать". Значения параметров надо брать оттуда, но их нельзя указать в декларации SqlDataSource, потому что в момент генерации страницы их там просто нет. Например, для того чтобы установить в режим редактирования строку DataGrid, необходимо присвоить свойству EditItemIndex элемента управления DataGrid значение индекса текущей строки.

В событии UpdateCommand в аргументе e находим текущую строку. Обновляемые данные находятся в TextBox-ах в ячейках 0 и 1. Чтобы выйти из режима редактирования, свойству EditItemIndex нужно присвоить значение - 1:

protected void UsersDataGrid_UpdateCommand(object source, 
System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
    TableCell cell=e.Item.Cells[0];
    TextBox cnt = (TextBox)cell.FindControl("NameTextBox");
    NameTextBox.Text = cnt.Text; 
 
    cell = e.Item.Cells[1];
    cnt = (TextBox)cell.FindControl("CommentTextBox");
 
    CommentTextBox.Text = cnt.Text;
    HiddenField1.Value = 
UsersDataGrid.DataKeys[e.Item.ItemIndex].ToString();
    cell = e.Item.Cells[2];
    CheckBox check = (CheckBox)cell.FindControl("Registered");
    Registered.Checked = check.Checked;
    SqlDataSource1.Update();
 
    UsersDataGrid.EditItemIndex = -1;
 
    UsersDataGrid.DataBind();
    NameTextBox.Text = "";
    CommentTextBox.Text = "";
    NameTextBox.Visible = true;
    CommentTextBox.Visible = true;
    Registered.Visible = true;
}

После обновления вновь читаем данные и делаем видимыми поля редактирования.

Отказ от редактирования без сохранения изменений обрабатывается в событии CancelCommand:

protected void UsersDataGrid_CancelCommand(object source, 
DataGridCommandEventArgs e)
{
    UsersDataGrid.EditItemIndex = -1;
    NameTextBox.Visible = true;
    CommentTextBox.Visible = true;
    UsersDataGrid.DataBind();
}

Заключение

Мы рассмотрели 3 элемента управления, существовавшие с ASP .NET 1.0. Repeater использует только шаблоны, DataList создает таблицу и пользуется шаблоном для отображения ее строк, а DataGrid может обходиться и без шаблонов. Примерно такая же система существует и у новых элементов управления ASP .NET 2.0.

Объектная модель упрощает связывание с данными, но ее реальная мощь проявляется при использовании новых элементов управления GridView, DetailsView, FormView, которые мы рассмотрим в следующей лекции.

В ASP .NET появились 3 новых элемента управления для отображения табличных данных: GridView, DetailsView и FormView.

GridView

Элемент управления GridView является усовершенствованным элементом, призванным заменить DataGrid. Все сказанное о DataGrid относится к GridView, но с немного другими названиями. Так, вместо BoundColumn употребляется BoundField, а в названиях стилей вместо Item находится Row. Таким образом, любой DataGrid можно преобразовать в GridView, но не наоборот. Хотя в простейшем варианте GridView отображает такую же таблицу, он наследник не DataGrid, а CompositeDataBoundControl.

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

В отличие от версии 1.1, новые элементы могут работать и на мобильных устройствах.

GridView вместе с SqlDataSource появится простым перетаскиванием таблицы Users на форму:

<asp:GridView ID="GridView1" runat="server" 
    AutoGenerateColumns="False" DataKeyNames="UID"
    DataSourceID="SqlDataSource1" 
    EmptyDataText="There are no data records to display.">
    <Columns>
        <asp:BoundField DataField="name" HeaderText="name" 
SortExpression="name" />
        <asp:BoundField DataField="Comments" 
HeaderText="Comments" SortExpression="Comments" />
    </Columns>
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:DemoBaseConnectionString1 %>"
        DeleteCommand="DELETE FROM [Users] WHERE [UID] = @UID" 
InsertCommand="INSERT INTO [Users] ([UID], [name], [Comments]) 
VALUES (@UID, @name, @Comments)"
        ProviderName="<%$ 
ConnectionStrings:DemoBaseConnectionString1.ProviderName %>"
    SelectCommand="SELECT [UID], [name], [Comments] FROM [Users]" 
UpdateCommand="UPDATE [Users] SET [name] = @name, [Comments] = 
@Comments WHERE [UID] = @UID">
    <InsertParameters>
        <asp:Parameter Name="UID" Type="Int32" />
        <asp:Parameter Name="name" Type="String" />
        <asp:Parameter Name="Comments" Type="String" />
    </InsertParameters>
    <UpdateParameters>
        <asp:Parameter Name="name" Type="String" />
        <asp:Parameter Name="Comments" Type="String" />
        <asp:Parameter Name="UID" Type="Int32" />
    </UpdateParameters>
    <DeleteParameters>
        <asp:Parameter Name="UID" Type="Int32" />
    </DeleteParameters>
</asp:SqlDataSource>

Установка свойства AllowSorting создает в заголовке гиперссылки, при нажатии на которые таблица будет сортироваться по выбранному полю. В этом проявляется преимущество перед DataGrid, для сортировки необходимо переопределить событие SortCommand (рис. 9.1).

После повторного нажатия на заголовок таблица сортируется по этому полю в убывающем порядке.

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

http://www.intuit.ru/department/se/aspdotnet/9/9_1.png


Рис. 9.1. 

Имя

Comments

Burda

Одесса, ведущий кулинар

Burda

Редактор журнала мод

Bush

Вашингтон, президент

Bush

Буш-отец

В таком случае поможет немного кодирования. Переопределим событие Sorting:

protected void GridView1_Sorting(object sender, 
GridViewSortEventArgs e)
{
    string oldExpression = GridView1.SortExpression;
    string newExpression = e.SortExpression;
    if (oldExpression.IndexOf(newExpression) < 0)
    {
        if (oldExpression.Length > 0)
             e.SortExpression = oldExpression + ", " + 
newExpression;
         else
             e.SortExpression = newExpression;
     }
     else
     {
         e.SortExpression = oldExpression;
     }
}

Отсортируем сначала по имени, потом по Comments:

Имя

Comments

Burda

Одесса, ведущий кулинар

Burda

Редактор журнала мод

Bush

Буш-отец

Bush

Вашингтон, президент

"Язык — не сын, а отец мысли."

Оскар Уайльд

Аббревиатура XML расшифровывается как Extensible Markup Language, в переводе "расширяемый язык разметки". Как и язык HTML, он является подмножеством SGML (Standard General Markup Language) — "дедушки" языков разметки. Мы уже не раз сталкивались с форматом XML: таков формат конфигурационных файлов или файла описания объектных источников данных.

XML — это универсальный, независящий от платформы стандарт описания информации, который можно использовать для представления иерархических данных и унификации передаваемой информации. Без его знания невозможно понимание SOAP и, следовательно, web-сервисов. XML стал де-факто стандартом передачи данных в сети Интернет. Стандарт XML и связанных с ним форматов определяется консорциумом W3C (World Wide Web Consortium). Например, мы создаем aspx-страницы в формате xHTML — переходном между HTML и XML, стандарт которого тоже определен W3C. Стандарт xHTML налагает более строгие правила на правильное формирование документа, аналогичные правилам XML.

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

Стандарт требует, чтобы программа, которая обрабатывает XML-документ, останавливала работу, когда обнаружит ошибку. А если браузер обнаружит непонятный тег в HTML или отсутствие закрывающего тега, он это просто игнорирует.

В начале XML-документа обязательно появляется его декларация, или пролог. В нем указывается версия стандарта XML, которому он соответствует:

<?xml version="1.0" encoding="utf-8" ?>

Декларация не является частью XML-документа и не имеет закрывающего тега. В тексте XML-файла могут находиться комментарии в стиле HTML — <!--text -->.

XML-документ может иметь только один корневой элемент. В него могут быть вложены другие узлы, а в них, в свою очередь, — другие. Каждому открывающему тегу XML должен соответствовать закрывающий тег. После завершающего тега корневого элемента не может быть других тегов. Теги XML чувствительны к регистру (case-sensitive). Теги должны быть целиком вложены друг в друга. поэтому код, допустимый в HTML

<b><i>Какой-то текст</b></i>

является ошибкой в XML.

У тегов могут быть атрибуты. Значения атрибутов должны быть заключены в кавычки. Порядок атрибутов значения не имеет. Между открывающим и закрывающим тегами может находиться текст. В XML сохраняются все пробелы, находящиеся в тексте. Если текста нет, можно применить сокращенную форму записи. Пример тега XML:

<PROPERTY Label="ogl_extension" Value="4520" Itemtype="predefined" 
/>

Это краткая форма тега

<PROPERTY Label="ogl_extension" Value="4520" Itemtype="predefined"></PROPERTY>

Вам это ничего не напоминает? Правила описания элементов ASP .NET точно такие же.

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

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

Документы XML, удовлетворяющие всем требованиям синтаксиса, называют правильными (well-formed). Для описания данных XML использует DTD (Document Type Definition) — определение типа документа. Если файл соответствует DTD, он считается действительным (valid).

Браузеры IE 6.0, FireFox 1.5 отображают XML-файлы с выделением синтаксиса. Родительские узлы можно раскрывать и закрывать. Например, в закрытом виде корневой узел файла BirthDay.xml выглядит так:

+ <BD>

Если его раскрыть, увидим

-  <BD>
- <Item Type="Plugin">
  <LinkText>Отправить поздравительную открытку</LinkText> 
  <PluginID>Friendship</PluginID> 
  <InitData /> 
  </Item>
- <Item Type="URL">
  <AdditionalText>Отправить поздравительную открыт-
ку</AdditionalText> 
  <URL>www.icq.com</URL> 
         </Item>
  </BD>

Среды разработки Visual Studio и VWD Express проверяют правильность XML-документов прямо во время редактирования.

AdRotator

Элемент управления AdRotator позволяет показывать рекламные баннеры и автоматически заменять их на другие. Сами баннеры описаны в файле XML или в другом источнике данных. Реклама обновляется каждый раз при обновлении страницы. В свойстве AdvertismentFile задается имя XML-файла. Скелет XML-файла таков:

<?xml version="1.0" encoding="utf-8" ?>
<Advertisements
xmlns="http://schemas.microsoft.com/AspNet/AdRotator-Schedule-
File">
</Advertisements>

Внутри узла Advertisements располагаются узлы <Ad> </Ad>

У этих узлов имеются 5 атрибутов, все они необязательны.

ImageUrl

Картинка, которая будет демонстрироваться при выборе данного объявления

NavigateUrl

Адрес, по которому будет совершен переход при щелчке на картинку

AlternateText

Альтернативный текст, если показ изображений выключен

Impressions

Все значения Impressions суммируются. Вероятность показа рекламы равна значению Impressions, деленному на эту сумму

Keyword

Ключевое слово-категория рекламы, позволяет фильтровать объявления

Пример файла AdvertismentFile (он называется ads.xml):

<?xml version="1.0" encoding="utf-8" ?>
<Advertisements
xmlns="http://schemas.microsoft.com/AspNet/AdRotator-Schedule-
File">
  <Ad>
    <ImageUrl>fixed.gif</ImageUrl>
    <NavigateUrl>http://www.im.am</NavigateUrl>
    <AlternateText>Бесплатный хостинг</AlternateText>
    <Impressions>40</Impressions>
    <Keyword>хостинг</Keyword>
  </Ad>
  <Ad>
    <ImageUrl>logo2.jpg</ImageUrl>
    <NavigateUrl>http://www.nv.am</NavigateUrl>
    <AlternateText>Газета "Новое время"</AlternateText>
    <Impressions>50</Impressions>
    <Keyword>новости</Keyword>
  </Ad>
  <Ad>
    <ImageUrl>summer.jpg</ImageUrl>
    <NavigateUrl>http://www.utro.ru</NavigateUrl>
    <AlternateText>Певицу Жасмин избил муж!</AlternateText>
    <Impressions>100</Impressions>
    <Keyword>желтые новости</Keyword>
  </Ad>
</Advertisements>

На страницу помещен элемент управления. Его свойство AdvertisementFile указывает на этот файл:

<asp:AdRotator ID="AdRotator1" runat="server" 
        AdvertisementFile="ads.xml" Height="164px" Width="574px" 
/>

Если установлено свойство Keyword, то элемент управления показывает только ту рекламу, которая соответствует его содержанию. Так как его можно менять динамически, есть возможность подстраивать рекламу под нужды пользователя. Keyword должен встречаться хотя бы один раз в файле объявлений, иначе вместо рекламы будет пустой прямоугольник.

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

<asp:AdRotator ID="AdRotator2" runat="server"
DataSourceId="SqlDataSource1" AlternateTextField="Alternate"
ImageUrlField="Image" NavigateUrlField="NavigateUrl" />

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

Известно, что для форматирования HTML-файлов часто используются CSS (Cascading Stylesheets), хотя это необязательно, так как браузеры соотносят со всеми тегами определенный внешний вид. Элемент <p> задает параграф, <B> — полужирный шрифт; браузер знает, как их показывать.

Поскольку XML не использует изначально заданные теги, их значение может быть каким угодно: <table> может означать таблицу HTML, а может и деревянный стол. Поэтому браузеры показывают XML-документы "как есть". Можно задать CSS-файлы и для XML-документов, но это не рекомендуется.

Для того чтобы задать формат отображения XML-документов, используются таблицы стилей XSL. XSL — расширяемый язык стилей (Extensible Stylesheet Language), он гораздо более богат возможностями, чем CSS. XSL — больше, чем просто таблица стилей.

Один и тот же файл XML можно связать с разными таблицами XSL, в том числе программно.

XSL состоит из трех частей:

  1. XSLT - метод преобразования XML-документов.
  2. XPath - метод задания частей и путей к элементам XML
  3. XSL Formatting Object - метод форматирования XML-документов.

Самая важная часть XSL - это язык преобразований XSLT (XSL Transformation). Он применяется для преобразования XSL-документов в другие типы документов или другие XSL-документы. Часто XSLT используется для преобразования XSL-документа в формат HTML.

Для того чтобы создать XSLT-документ, выберите в диалоге создания файла XSLT file. VS 2005 создает каркас таблицы стилей. Так как таблица стилей сама по себе является XML-документом, она начинается с декларации XML:

<?xml version="1.0" encoding="utf-8"?>

Тег xsl:stylesheet задает начало таблицы стилей:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
</xsl:stylesheet>

Если вы изучали CSS, то знаете, что для задания стилей используются правила. Правило состоит из селектора и описания стиля в фигурных скобках.

a
{
   font-size:medium;
   color:Fuchsia;
}

Это правило определяет стиль вывода гиперссылок среднего размера шрифтом фиолетовым цветом.

В XSL применяются шаблоны. Для связывания шаблона с XML-элементом используется атрибут соответствия.

Тег xsl:template задает начало шаблона. Атрибут шаблона match="/"> связывает шаблон и корневой элемент исходного XML-документа:

<xsl:template match="/">
</xsl:template>

В этот тег вложен шаблон HTML-файла. Комментарий напоминает о том, что туда нужно вставить XSL-элементы.

Создание файлов XSLT рассмотрим на примере. Создайте в папке App_Data файл XML "Quotes.xml":

<?xml version="1.0" encoding="utf-8" ?>
<!--цитаты великих людей -->
 <Quotes>
  <Quote>
    <Text>
      Хотели как лучше, а получилось как всегда.</Text>
      <Author>Виктор Черномырдин</Author>
    </Quote>
  <Quote>
    <Text>Америка - континент, названный так потому, что его от-
крыл Колумб.</Text>
    <Author>Жорж Элгози</Author>
  </Quote>
  <Quote>
    <Text>Я прихожу в бешенство от одной мысли о том, сколько бы
 я всего узнал, если бы не ходил в школу.</Text>
    <Author>Джордж Бернард Шоу</Author>
  </Quote>
  <Quote>
    <Text>Многое придумано для того, чтобы не думать.</Text>
    <Author>Карел Чапек</Author>
  </Quote>
  <Quote>
    <Text>Если скажешь правду, все равно рано или позно попадешь-
ся.</Text>
    <Author>Оскар Уайльд</Author>
  </Quote>
  <Quote>
    <Text>Быть ему президентом, если его до той поры не пове-
сят.</Text>
    <Author>Марк Твен</Author>
  </Quote>
</Quotes>

Чтобы внести в выходной поток XSLT-преобразования каждый XML-элемент, применяется тег XSL xsl:for-each. Элемент :for-each определяет местоположение элементов в XML-документе и повторяет шаблон для каждого из них:

    <xsl:for-each select="Quotes/Quote"> </xsl:for-each>

Все, что находится в шаблоне, будет выводиться столько раз, сколько в исходном документе встретится элемент Quote, заключенный в тег Quotes.

Чтобы внести в выходной поток XSLT-преобразования значение XML-элемента, применяется тег XSL xsl:value-of:

    <xsl:value-of select="Text"/>
    <hr/>

Данные можно отсортировать с помощью тега xsl:sort, который должен находиться внутри элемента xsl:for-each:

    <xsl:sort select="Author" />

XSL может применять условия для показа и форматирования информации в зависимости от значений элементов. "Условный оператор" имеет вид <xsl:choose>, в который вложены элементы <xsl:when> и, возможно, <xsl:otherwise>. Условие задается в элементе <xsl:when> с помощью парамерта test:

    <xsl:choose>
      <xsl:when test="Author='Марк Твен'">
        <img src="http://www.tvkultura.ru/p/q_14406.jpg"></img>
          </xsl:when>

Окончательный вид файла трансформации:

<?xml version="1.0" encoding="utf-8"?>
 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  <xsl:template match="/">
    <html>
      <body>
        <h1 style="background-color: RoyalBlue; color: white;
          font-size: 24pt; text-align: center; letter-spacing: 1.0em">
          Известные цитаты
        </h1>
        <table border="0">
          <tr style="font-size: 12pt; font-family: verdana;
            font-weight: bold">
            <td style="text-align: center">Цитата</td>
            <td style="text-align: center">Автор</td>
          </tr>
          <xsl:for-each select="Quotes/Quote">
            <xsl:sort select="Author" />
            <tr style="font-size: 10pt; font-family: verdana">
              <td>
                <xsl:value-of select="Text"/></td>
              <td>
                <xsl:choose>
                  <xsl:when test="Author='Марк Твен'">
                     <img src="http://www.tvkultura.ru/ p/q_14406.jpg"></img>
                  </xsl:when>
                  <xsl:otherwise>
                  <i><xsl:value-of select="Author"/></i>
                  </xsl:otherwise>
                </xsl:choose>
              </td>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Если хотите увидеть результат преобразования документа в браузере, включите после XML-декларации объявление

<?xml-stylesheet type="text/xsl" href="XSLTFile.xsl"?>,

или выберите в меню XML пункт "Show XML Output" и определите файл преобразования.

Тот же самый XML-документ можно преобразовать с помощью другого XSL-файла:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
    <html>
    <body>
      <h1 style="background-color: Brown; color: white;
        font-size: 24pt; text-align: center; letter-spacing: 1.0em">
        Мастера Афоризма
      </h1>
      <xsl:for-each select="Quotes/Quote">
          <xsl:value-of select="Text"/>
        <br/>
          <xsl:value-of select="Author"/>
        <hr width="70%"/>  </xsl:for-each>
    </body>
    </html>
</xsl:template>
</xsl:stylesheet>

В результате будут выводиться цитаты, разделенные горизонтальной линией.

Файлы определения схемы документа

Согласно современному стандарту, валидный документ должен соответствовать связанному с ним файлу XSD (XML Schema Definition) — файлу определения схемы XML, который определяет конкретный язык, то есть описывает, какие элементы и типы могут появляться в документе. Схемы XSD призваны заменить DTD (Document Type Definition), разница между ними заключается в том, что файлы XSD сами тоже используют синтаксис XML. Схемы XSD позволяют определить, какие теги разрешены, обязательны они или нет, могут ли повторяться в документе и так далее. Таким образом, XML описывает данные, а XSD — структуру этих данных, или метаданные. В терминах программирования, XSD — описание типов, в то время как в XML-файле описаны объекты этих типов. По адресу http://www.w3.org/TR/2003/WD-xmlschema-11-req-20030121/ находится рабочий проект стандарта XSD 1.1.

Файл описания схемы начинается с описания префикса пространства имен, который включается затем во все элементы этого файла. Адрес http://tempuri.org предназначается для задания URI для пространств имен ASP .NET:

<xs:schema id="XMLSchema2" targetNamespace= 
"http://tempuri.org/XMLSchema2.xsd" 
           elementFormDefault="qualified" 
  xmlns="http://tempuri.org/XMLSchema2.xsd" 
  xmlns:mstns="http://tempuri.org/XMLSchema2.xsd" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema">

Создавая схемы XSD, можно:

1. декларировать элементы и атрибуты.

    <xs:element name="Author" type="xs:string" default="Пушкин" 
minOccurs="1" maxOccurs="1" />

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

        <xs:element name="Child" type=" xs:string" 
maxOccurs="unbounded"/>

Параметр maxOccurs="unbounded" указывает, что элемент может встречаться любое количество раз.

Параметр ref позволяет ссылаться на уже описанный в данном файле глобальный элемент или атрибут, чтобы избежать повторного описания одних и тех же элементов.

2. определить простые и сложные типы.

В XSD есть предопределенные типы — примерно такие же, как в .NET. Во время работы приложения они преобразуются в типы .NET. На их основании можно строить сложные типы, похожие на структуры языков программирования. Сложный тип состоит из последовательности описаний элементов. Определим сложный тип:

  <xs:complexType name="Quote">
    <xs:sequence>
      <xs:element name="Text" type="xs:string" minOccurs="1" 
maxOccurs="1" />
      <xs:element name="Author" type="xs:string" default="Пушкин" 
minOccurs="1" maxOccurs="1" />
    </xs:sequence>
  </xs:complexType>

Тег <xs:sequence> определяет, что элементы в данном типе должны появляться в заданном порядке. Если бы использовался тег <xs:all>, то порядок появления элементов мог бы быть любым.

Тег <xs:choice> похож на структуру с вариантами. Он определяет, что в элементе данного типа должен быть только один из вложенных элементов:

<xs:complexType name="StateProvinceType">
  <xs:choice>
    <xs:element name="State" type="xs:string"/>
    <xs:element name="Province" type="xs:string"/>
  </xs:choice>
</xs:complexType>

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

Глобальный тип можно использовать в определении элементов:

    <xs:element name="Quote" type="Quote" maxOccurs="unbounded" />

В следующем примере определен простой тип, вложенный в определение элемента MyValue:

<xs:element name="MyValue" type="MyInteger"/>
<xs:simpleType name="MyInteger">
   <xs:restriction base="xs:positiveInteger">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="10"/>
   </xs:restriction>
</xs:simpleType>

Значениями этого типа могут быть целые положительные числа от 1 до 10.

Простой тип может быть перечислением:

  <xs:simpleType name="Answers">
    <xs:restriction base="xs:string">
      <xs:enumeration value="yes">
      </xs:enumeration>
      <xs:enumeration value="no" />
      <xs:enumeration value="don't know" />
    </xs:restriction>
  </xs:simpleType>

3. добавлять новые группы и группы атрибутов.

В определении сложного типа могут фигурировать атрибуты. Предположим, мы хотим построить схему такого файла:

<?xml version="1.0" encoding="utf-8" ?>
<FilmChoices>
  <Film Title='Броненосец "Потемкин"'>
    <Year>1925</Year>
    <Director>Эйзенштейн</Director></Film>
  <Film Title="Война и мир">
    <Year>1967</Year>
    <Director>Сергей Бондарчук</Director> </Film>
  <Film Title ="Девятая рота"> 
    <Year>2005</Year>
    <Director>Федор Бондарчук</Director> </Film>
</FilmChoices>

Необходимо потребовать наличие атрибута Title:

    <xs:attribute name="Title" type="xs:string"  use="required"/>

Атрибуты могут быть только простых типов.

4. добавлять аннотации.

Аннотации позволяют вставлять описание существующих элементов — таким образом, в файл добавляется документация:

  <xs:complexType name="Quote">
    <xs:annotation>
      <xs:documentation>
        Цитаты разных авторов
      </xs:documentation>
    </xs:annotation>

<xs:documentation> предназначается для читателей файла, а <xs:appinfo> — для обрабатывающих файл программ.

Полное описание синтаксиса XSD можно прочитать по адресу http://www.w3.org/2001/XMLSchema.xsd.

Редактировать XSD-файлы в Visual Studio 2005 можно и через исходный код, и с помощью дизайнера. Для XML-документа можно автоматически сгенерировать соответствующую ему схему. В окне свойств XML-документа можно задать как файл схемы, так и файл преобразования. В таком случае студия автоматически проверяет файл на соответствие схеме и IntelliSense даже подставляет теги из этого файла.

Класс XmlReader

С помощью класса XmlReader можно быстрее, чем другими методами, получить данные из XML-документов.

XmlReader — это абстрактный класс. Чтобы начать чтение, в статический метод Create передается объект класса XmlReaderSettings. Эта функция подсчитывает число узлов в документе:

using System.Xml;
using System.IO;
 
    private int CountNodes(string xmlFile)
    {
        int NodesCount=0;
        XmlReaderSettings settings = new XmlReaderSettings();
        settings.IgnoreWhitespace = true;
        settings.IgnoreComments = true;
 
        using (XmlReader reader = XmlReader.Create(xmlFile, set-
        tings))
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    NodesCount++;
                }
            }
        }
        return NodesCount;
    }

Класс XmlReader позволяет извлекать из документа классы CLR. Пусть у нас есть меню ресторана:

<?xml version="1.0"?>
<pizza_menu>
  <food name="Пицца Грандиозо">
    <price>450.00</price>
    <description>Грибы, бекон, салями, ветчина,
    баварские сосиски, артишоки,
    высушенные на солнце помидоры,
    сыр Пармезан </description>
    <calories>700</calories>
  </food>
  <food name="Прэго пицца">
    <price>306.00</price>
    <description>
      Кусочки нежной куриной грудки в соусе Песто,
      красный сладкий перец, грибы,
      кукуруза, сыр Пармезан
    </description>
    <calories>650</calories>
  </food>
  <food name="Пицца Маргарита">
    <price>126.00</price>
    <description>
      Классическая итальянская пицца
      подается на выбор с базиликом
      или без базилика
    </description>
    <calories>600</calories>
  </food>
</pizza_menu>

Напишем функцию, которая посчитает сумму цен и количества калорий в меню:

    protected void Page_Load(object sender, EventArgs e)
    {
        int ItemsCount = 0;
        decimal DishesTotal = 0;
        UInt16 CaloriesTotal = 0;
        XmlReaderSettings settings = new XmlReaderSettings();
        settings.IgnoreWhitespace = true;
        NameTable nt = new NameTable();
        object food = nt.Add("food");
        object price = nt.Add("price");
        object calories = nt.Add("calories");
        settings.NameTable = nt;
        string MenuFile = 
        Path.Combine(Request.PhysicalApplicationPath, "menu.xml");
        using (XmlReader reader = XmlReader.Create(MenuFile, set-
        tings))
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element &&
                    food.Equals(reader.LocalName))
                {
                    ItemsCount++;
                }
                if (reader.NodeType == XmlNodeType.Element &&
                    price.Equals(reader.LocalName))
                {
                    DishesTotal +=
                    (UInt16)reader.ReadElementContentAsDecimal();
                }
                if (reader.NodeType == XmlNodeType.Element &&
                calories.Equals(reader.LocalName))
                {
                    CaloriesTotal +=
                    (UInt16)reader.ReadElementContentAsInt();
                }
            }
        }
        Response.Write(String.Format("Вы заказали {0} блюда на 
        сумму {1:C}, {2} калорий",
        ItemsCount, DishesTotal, CaloriesTotal));

Класс XPathDocument

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

Возьмем XML-документ "Quotes.xml" и файл трансформации XSL "Quotes.xsl". В выходной поток страницы будет направлен результат преобразования XML-документа:

    <%
        XPathDocument doc =
          new XPathDocument(Server.MapPath("App_Data\\Quotes.xml"));
              XslCompiledTransform xsl = new XslCompiledTransform();
              xsl.Load(Server.MapPath("App_Data\\Quotes.xsl"));
              xsl.Transform(doc, null, Response.OutputStream); %>

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

Элемент управления XML

Элемент управления XML предоставляет способ преобразовать XML-документ, используя таблицу стилей XSL. Свойство DocumentSource позволяют задать XML-файл, в котором находятся данные, а TransformSource — файл трансформации XSLT.

В предыдущем примере того же результата можно достичь, если поставить на странице элемент управления XML:

    <asp:Xml ID="Xml1" runat="server" 
DocumentSource="~/App_Code/Quotes.xml" TransformSource="~/App_Data 
/Quotes.xsl"></asp:Xml>

XMLDataSource

Элемент-источник данных XMLDataSource обеспечивает простой способ подключения XML-документов как источников данных к элементам, отображающим информацию. Также можно задать запрос XPath для того, чтобы отфильтровать данные. Как и SqlDataSource, он позволяет редактировать, удалять, добавлять записи данных. Для этого нужно получить доступ к находящемуся в нем объекту XmlDataDocument с помощью вызова метода GetXmlDocument. После редактирования документ сохраняется с помощью метода Save.

В отличие от табличных данных в СУБД, данные в XML-файлах иерархичны, поэтому XMLDataSource удобно привязывать к иерархичным элементам управления, например, Menu.

Синтаксис привязки к данным XML

Так как в приложениях XML-данные используются все чаще и чаще, был введен метод привязки данных, полученных из XMLDataSource.

Эти методы работают так же, как Bind и Eval, которые обсуждались в лекции 7:

    <% XPathBinder.Eval(Container.DataItem, "name"); %>

Как и при связывании с помощью SQLDataSource, можно сокращенно писать

    <%# XPath("name")%>

Так же как и у DataBinder, метод Eval класса XPathBinder поддерживает строки форматирования:

<% XPath("employees/employee/HireDate", "{0:mm dd yyyy}") %>

Применим этот синтаксис в элементе DataList, который получает данные из источника данных XmlDataSource:

<asp:XmlDataSource ID="XmlDataSource1" runat="server" 
DataFile="~/nobel.xml" XPath="//nobel/literature/writer">
</asp:XmlDataSource>
 
<asp:DataList ID="DataList1" DataSourceID="XmlDataSource1" 
runat="server">
    <ItemTemplate>
         <p>
             <%# XPath("name")%>  получил премию по литературе в
             <%# XPath("winningdate")%>
             за произведение <%# XPath("work")%></b>
        </p>
    </ItemTemplate>
</asp:DataList>

Заключение

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

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

Курсы   Учебные программы   Учебники   Новости   Форум   Помощь

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

Структура навигации должна быть описана в карте сайта. Она находится в файле .sitemap формата XML, который можно создать в диалоге New File, выбрав пункт Site Map. Имя этого файла по умолчанию — web.sitemap. Карта сайта служит источником информации для всех элементов управления группы Navigation. С ней можно работать программно с помощью класса SiteMap или через элемент управления — источник данных SiteMapDataSource.

Узлы siteMapNode могут вкладываться друг в друга, создавая иерархию. Логика вложенности узлов никак не связана с физическим расположением файлов. Каждый атрибут url в файле .sitemap должен быть уникальным.

Схема формата .sitemap:

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
   elementFormDefault="qualified">
  <xs:element name="siteMap">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="siteMapNode" maxOccurs="unbounded" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="siteMapNode">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="siteMapNode" minOccurs="0" 
        maxOccurs="unbounded"/>
      </xs:sequence>
      <xs:attribute name="url" type="xs:string" />
      <xs:attribute name="title" type="xs:string" />
      <xs:attribute name="description" type="xs:string" />
      <xs:attribute name="keywords" type="xs:string" />
      <xs:attribute name="roles" type="xs:string" />
      <xs:attribute name="siteMapFile" type="xs:string" />
      <xs:attribute name="Provider" type="xs:string" />
    </xs:complexType>
  </xs:element>
</xs:schema>

Чтобы сделать карту сайта доступной для элементов управления, используется провайдер типа XmlSiteMapProvider. Можно разработать собственный провайдер.

Редактирование карты сайта в Visual Studio 2005 облегчается с помощью технологии IntelliSense.

Атрибут title узла карты сайта создает текстовое описание страницы. Он используется как текст гиперссылки, создаваемой в TreeView или Menu. Атрибут description задает текст подсказки (Tooltip), связанной с этой гиперссылкой. Атрибут url описывает путь к странице внутри web-сайта. При этом для страниц в корневой директории достаточно указать их название. Если страница находится в поддиректории, путь указывается с помощью прямого слэша:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap>
  <siteMapNode title="Home" url="Default.aspx">
    <siteMapNode title="Resume" url="Resume.aspx" />
    <siteMapNode title="Links" url="Links.aspx" />
    <siteMapNode title="Albums" url="Albums.aspx" >
      <siteMapNode title="Photos" url="Photos.aspx" >
        <siteMapNode title="Details" url="Details.aspx" />
      </siteMapNode>
    </siteMapNode>
    <siteMapNode title="Register" url="Register.aspx" />
    <siteMapNode title="Manage" url="Admin/Albums.aspx" >
      <siteMapNode title="Photos" url="Admin/Photos.aspx" >
        <siteMapNode title="Details" url="Admin/Details.aspx" />
      </siteMapNode>
    </siteMapNode>
  </siteMapNode>
</siteMap>

Элементы управления для навигации по сайту — TreeView, Menu, SiteMenuPath.

Некоторые элементы навигации могут работать с картой напрямую, например SiteMenuPath, но Menu и TreeView могут показывать карту сайта, только получая данные из SiteMapDataSource.

Элемент управления SiteMapPath

Подобную панель, которая показывает путь от главной страницы к текущей, часто называют breadcrumb ("хлебные крошки"):

MSDN Home > ASP.NET Developer Center > Reference > Using ASP.NET Controls

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

Для того чтобы на странице работал этот элемент, даже не нужно источника данных. Он автоматически читает карту сайта из файла Web.sitemap. Достаточно просто перетащить его на страницу. Имеются 4 свойства стиля, каждый из которых задается отдельно: для корневого элемента, для разделителя, обычного узла и текущего узла. У SiteMapPath имеется такая же возможность автоформатирования, как и у многих других элементов управления.

Свойство PathDirection позволяет изменить направление от корня к текущей странице на обратное. Например, если путь у вас был таким:

Главная : Игра : Таблица,

то после изменения значения PathDirection на CurrentToRoot станет таким:

Таблица : Игра : Главная

Текстовый атрибут PathSeparator задает разделитель между узлами. Например, в первом примере это " > ", который ставится по умолчанию, а во втором " : ". Пробелы здесь существенны. Похожие атрибуты были и в календаре — к примеру NextMonthText. Для того чтобы задать изображения, в качестве разделителя можно использовать шаблон PathSeparatorTemplate:

   <asp:SiteMapPath ID="SiteMapPath1" runat="server">
     <PathSeparatorTemplate>
       <asp:Image ID="Image1" runat="server" ImageUrl="img/fold-
   er.gif" />
     </PathSeparatorTemplate>
   </asp:SiteMapPath>

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

Если подержать курсор мыши над элементом, появится подсказка, текст которой берется из атрибута description соответствующего узла карты сайта. Отключить отображение подсказки можно с помощью свойства ShowToolTips="false".

Всего имеется 4 шаблона: PathSeparatorTemplate, NodeTemplate, RootTemplate и CurrentNodeTemplate, с помощью которых можно вставлять любые элементы управления в различные части SiteMapPath. Для каждой из частей можно определить и собственный стиль.

SiteMapDataSource

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

В простейшем виде объявляется так:

<asp:SiteMapDataSource ID="SiteMapDataSource1" Runat="server" />

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

StartFromCurrentNode =False задает возможность читать только узлы, начиная с текущей страницы.

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

SiteMapProvider может быть полезным при написании собственных провайдеров карты сайта.

SiteMapViewType определяет форму представления узлов. По умолчанию это Tree. Если значение равно Path, то будет читаться путь между корневым узлом и текущим, как в элементе управления SiteMapPath.

TreeView

Элемент TreeView создан специально для показа иерархической информации. Он может черпать информацию как из любого XML-файла через XmlDataSource, так и из карты сайта посредством SiteMapDataSource. Как следует из его названия, TreeView показывает данные в виде дерева, причем его узлы можно раскрывать и закрывать, выбирать отдельные "листья". При этом будут запускаться события, которые можно обработать.

TreeView состоит из узлов, которые соединены между собой отношениями "родитель—потомок". У одного родителя может быть один или несколько потомков. Узлы, у которых нет родителя, называются корневыми. Их в элементе управления может быть несколько. Узлы, у которых нет потомков, называются листьями.

При декларации TreeView на странице узлы описываются тегами TreeNode. Допускается любой уровень вложенности узлов друг в друга. Узлы элемента управления можно редактировать визуально:

<asp:TreeView ID="TreeLibrary" runat="server" 
ImageSet="WindowsHelp" >
   <Nodes>
      <asp:TreeNode Text="Категории книг" Value="Book 
Categories">
         <asp:TreeNode Text="Художественная литература" 
Value="literature">
            <asp:TreeNode Text="Русская классика" Value="Russian 
Classics" NavigateUrl="~/libru.aspx?id=1">
               <asp:TreeNode Text="Пушкин" Value="Pushkin" 
NavigateUrl="~/libru.aspx?id=1&auth=10">
               </asp:TreeNode>
            </asp:TreeNode>
         </asp:TreeNode>
         <asp:TreeNode Text="Компьютерная литература" 
Value="Computers">
            <asp:TreeNode Text="Web Development" Value=" Web 
Development" NavigateUrl="~/example1.aspx?id=1">
               <asp:TreeNode Text="JavaScript" Value=" 
JavaScript " NavigateUrl="~/example1.aspx?id=2">
               </asp:TreeNode>
               <asp:TreeNode Text="ASP.NET" Value="ASP.NET" 
NavigateUrl="~/example1.aspx?id=3"></asp:TreeNode>
            </asp:TreeNode>
         </asp:TreeNode>
      </asp:TreeNode>
   </Nodes>
</asp:TreeView>

Если нужно программно добавлять дочерние узлы, свойство PopulateOnDemand нужно установить в True.

Внешний вид TreeView можно менять самым разнообразным способом. Можно включить показ линий, соединяющих узлы:

ShowLines="True"

Внешний вид линий можно отредактировать, при этом с помощью средства TreeView Line Generator будут созданы изображения для всех ее фрагментов.

Вместо текста в узлах можно показывать изображения, заданные свойством ImageUrl.

Свойство ImageSet имеет набор предопределенных значков для разных типов узлов. Например, MSDN придаст вашему дереву сходство с TreeView на сайте msdn.com, а XPFileExplorer — с программой Explorer в Windows XP.

http://www.intuit.ru/department/se/aspdotnet/11/11_1.png


Рис. 11.1. 

В качестве изображения для узлов можно задать любые картинки:

CollapseImageUrl="Images/CollapseImage.gif"
ExpandImageUrl="Images/ExpandImage.gif"
LeafImageUrl=Images/LeafImage.gif">

Если источником данных служит XmlDataSource, то его узлы можно привязать к элементу TreeView. Создайте на странице TreeView. У него есть "умный ярлык", который позволит настроить источник данных. Настройка происходит так же, как и у элемента управления Xml.

http://www.intuit.ru/department/se/aspdotnet/11/11_2.png


Рис. 11.2. 

После этого нужно настроить способы показа данных. По умолчанию он будет показывать названия узлов, а не их внутреннее содержание. В SmartTag выберите пункт Edit TreeNode Databindings. В результате в редакторе должен появиться примерно такой текст:

<asp:XmlDataSource ID="XmlDataSource1" runat="server" 
        DataFile="~/FilmChoices.xml"
            XPath="FilmChoices/Film"></asp:XmlDataSource>
<asp:TreeView ID="TreeView1" runat="server" 
   DataSourceID="XmlDataSource1" Width="405px">
   <DataBindings>
      <asp:TreeNodeBinding DataMember="Film" TextField="Title" />
      <asp:TreeNodeBinding DataMember="Year" FormatString="Год 
{0}" TextField="#InnerText" />
      <asp:TreeNodeBinding DataMember="Director" 
FormatString="Режиссер {0}" TextField="#InnerText" />
   </DataBindings>
</asp:TreeView>

Значение TextField используется, если нужно показать значения атрибутов узла в исходном XML-файле, а #InnerText указывает текст между открывающими и закрывающими тегами узла.

Если выбираем источником данных SiteMap, то на странице создается еще один элемент управления — SiteMapDataSource:

<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
<asp:TreeView ID="TreeView1" runat="server" 
DataSourceID="SiteMapDataSource1"
    ShowLines="True" BackColor="WhiteSmoke" BorderStyle="Outset"
    ImageSet="BulletedList" Font-Names="Verdana" Font-
Overline="False"
    ForeColor="#804040" Width="199px" >
</asp:TreeView>

На странице элемент TreeView будет выглядеть так:

http://www.intuit.ru/department/se/aspdotnet/11/11_3.png


Рис. 11.3. 

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

Программное управление TreeView

У TreeView есть множество событий. Событие SelectedNodeChanged запускается, когда пользователь выбирает узел:

    protected void TreeLibrary_SelectedNodeChanged(object sender, 
EventArgs e)
    {
        Label1.Text = "Вы выбрали категорию " + 
TreeLibrary.SelectedNode.Text;
    }

Можно программно раскрывать и закрывать узлы:

TreeView1.ExpandAll();
TreeView1.CollapseAll();

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

В следующем примере заполним значения элемента управления TreeView из базы данных Northwind. Родительские узлы — категории продуктов, которые заполняются данными о продуктах тогда, когда узел необходимо раскрыть:

      <asp:TreeView ID="TreeViewCatProd" runat="server" 
ImageSet="BulletedList3" 
      OnTreeNodePopulate="TreeViewCatProd_TreeNodePopulate" 
ForeColor="DarkOliveGreen">
      </asp:TreeView>
 
public partial class Products : System.Web.UI.Page
{
  string connectionString = @"Data Source=.\SQLEXPRESS;Initial 
Catalog=Northwind;Integrated Security=True";
 
  protected void Page_Load(object sender, EventArgs e)
  {
    if (!Page.IsPostBack)
    {
      BindData();
    }
  }

Вспомогательная функция устанавливает соединение с базой данных и возвращает SqlDataReader — самый быстрый способ чтения данных:

  protected SqlDataReader CreateSqlDataReader(string SelectQuery, 
string ConnectionString)
  {
    SqlConnection  Connection = new 
SqlConnection(ConnectionString);
      SqlCommand Command = new SqlCommand(SelectQuery, Connection);
    try
    {
      Command.CommandType = CommandType.Text;
      Command.Connection.Open();
      SqlDataReader reader = 
Command.ExecuteReader(CommandBehavior.CloseConnection);
      return reader;
    }
    catch
    {
      Connection.Close();
      return null;
    }
  }

При работе с базами данных важно перехватывать исключения:

  protected void BindData()
  {
    TreeNode parentNode = null;
    SqlDataReader reader = CreateSqlDataReader("Select 
CategoryID,CategoryName from Categories", connectionString);
    if (reader!=null)
    {
      while (reader.Read())
      {
        parentNode = new 
TreeNode(reader["CategoryName"].ToString(), 
reader["CategoryID"].ToString());
        parentNode.Collapse();
        parentNode.PopulateOnDemand = true;
        parentNode.SelectAction = TreeNodeSelectAction.Expand;
        TreeViewCatProd.Nodes.Add(parentNode);
      }
      reader.Close();
    }
 }

Конструктор TreeNode может вызываться без параметров, но он перегружен. Вариант, который здесь используется, позволяет задать текст узла и значение Value (заполняется значением CategoryID), которое необходимо, чтобы найти в базе продукты этой категории.

При раскрытии узла с категорией будет вызываться обработчик:

  protected void TreeViewCatProd_TreeNodePopulate(object sender, 
TreeNodeEventArgs e)
  {
    TreeNode node = e.Node;
    if (node.PopulateOnDemand)
    {
      string command = string.Format("Select ProductID, 
ProductName from Products where CategoryID={0}", node.Value);
      SqlDataReader reader = CreateSqlDataReader(command, 
connectionString);
      node.ChildNodes.Clear();
      if (reader != null)
      {
        while (reader.Read())
        {
          TreeNode childNode = new 
TreeNode(reader["ProductName"].ToString());
          childNode.SelectAction = TreeNodeSelectAction.None;
          node.ChildNodes.Add(childNode);
        }
        node.Expand();
        reader.Close();
      }
    }
  }

TreeView позволяет не только показывать информацию, но ставить флажки рядом с узлами. Это полезно, если в нем содержится информация о товарах и пользователь может выбрать некоторые из них. Свойство ShowCheckBoxes допускает 5 значений: None, Root, Parent, Leaf, All:

ShowCheckBoxes="Leaf"

При этом рядом с узлами-листьями появляются флажки. Значение флажков можно прочитать программно:

<asp:XmlDataSource ID="XmlDataSource3" runat="server" 
DataFile="~/menu.xml"></asp:XmlDataSource>
<asp:TreeView ID="TreeMenu" runat="server" 
DataSourceID="XmlDataSource3" BackColor="#FFFBD6" 
   Font-Names="Verdana" Font-Size="0.8em" ForeColor="#990000" 
Height="78px"
   Width="415px" ShowCheckBoxes="Parent" 
OnTreeNodeCheckChanged="TreeMenu_TreeNodeCheckChanged" 
NodeWrap="True" PopulateNodesFromClient="False">
   <DataBindings>
      <asp:TreeNodeBinding DataMember="name" 
ValueField="#InnerText" />
      <asp:TreeNodeBinding DataMember="price" FormatString="{0} 
руб." TextField="#InnerText" ValueField="#InnerText" />
      <asp:TreeNodeBinding DataMember="description" 
TextField="#InnerText" />
      <asp:TreeNodeBinding DataMember="calories" 
FormatString="{0} калорий" TextField="#InnerText" />
      <asp:TreeNodeBinding DataMember="food" TextField="name" />
      <asp:TreeNodeBinding DataMember="pizza_menu" Text="Меню 
Пиццы" Value="Меню Пиццы" />
   </DataBindings>
</asp:TreeView>
<asp:Label ID="Label1" runat="server" ></asp:Label>

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

    protected void TreeMenu_TreeNodeCheckChanged(object sender, 
TreeNodeEventArgs e)
    {
        decimal sumPrices=0.0M;
        if (TreeMenu.CheckedNodes.Count > 0)
        {
            Label1.Text = "";
            foreach (TreeNode node in TreeMenu.CheckedNodes)
            {
              sumPrices += decimal.Parse(node.ChildNodes[0].Value);
            }
            Label1.Text = sumPrices.ToString() +"<br>";
        }
    }

Цена записана в первом дочернем поле с индексом 0. При этом обращаться к свойству Text было бы неправильно, потому что там находится отформатированный текст, например, "306 руб.", который нельзя преобразовать в число.

Элемент управления Menu

Выпадающее меню можно создать средствами одного только css. Это красивое решение, но требует большого объема кода. Также необходимо предусмотреть возможность просмотра разными браузерами. Большинство разработчиков создают меню с помощью JavaScript. В ASP .NET 2.0 создание выпадающего меню любого уровня вложенности требует всего двух строчек:

<asp:SiteMapDataSource  ID="SiteMapDataSource1" runat="server" />
<asp:Menu ID="Menu1" runat="server" 
DataSourceID="SiteMapDataSource1"></asp:Menu>

Вся остальная работа достается ASP .NET. А ее будет много! Чтобы убедиться в этом, посмотрите сгенерированный код в браузере.

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

Menu допускает горизонтальную и вертикальную ориентацию, которая задается свойством Orientation. При горизонтальной ориентации можно получить полоску меню. Меню состоит из статической и динамической частей, каждый — со своим набором стилей.

StaticHoverStyle

DynamicHoverStyle

StaticMenuItemStyle

DynamicMenuItemStyle

StaticMenuStyle

DynamicMenuStyle

StaticSelectedStyle

DynamicSelectedStyle

StaticTemplate

DynamicTemplate

Стили можно поменять "одним махом" с помощью автоформатирования.

Статические пункты отображаются постоянно, а динамические — при выборе родительских узлов. Для этого автоматически генерируется код JavaScript.

Свойство StaticDisplayLevels по умолчанию равно 1, то есть показываются только главные пункты меню, а остальные появляются в момент наведения мышки. Если это значение изменить, получим статическое многоуровневое меню. На странице разные меню можно привязать к разным источникам SiteMapDataSource, тогда при одной карте сайта они будут показывать его разные подмножества:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-
1.0" >
  <siteMapNode title="Main" >
    <siteMapNode title=" Главная" url="Default.aspx">
      <siteMapNode url="courses.aspx" title="Курсы" descrip-
tion="Курсы" />
      <siteMapNode url="speciality.aspx" title="Учебные программы" 
description="" />
      <siteMapNode url="shop.aspx" title="Учебники" description="" />
      <siteMapNode url="News.aspx" title="Новости" description="" />
      <siteMapNode url="Forum.aspx" title="Форум"/>
      <siteMapNode url="Help.aspx" title="Помощь"/>
    </siteMapNode>
    <siteMapNode title="user" url="Default2.aspx">
      <siteMapNode url="login.aspx" title="Регистрация" description="" />
      <siteMapNode title="||" >
      <siteMapNode url="records.aspx" title="Зачетка" description="" />
      <siteMapNode url="diploms.aspx" title="Дипломы" description="" />
      </siteMapNode>
      <siteMapNode title="||" >
      <siteMapNode url="user.aspx" title="Настройки" description="" />
      <siteMapNode url="cart.aspx" title="Корзина"/>
      <siteMapNode url="history.aspx" title="Заказы"/>
      <siteMapNode url="account.aspx" title="Личный счет"/>
      </siteMapNode>
    </siteMapNode>
  </siteMapNode>
</siteMap>

Одно меню должно показывать разделы сайта, второе — возможности пользователя:

    <asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" 
ShowStartingNode="False" StartingNodeUrl="~/Default.aspx" />
    <asp:Menu ID="Menu1" runat="server" BackColor="#FFFBD6" 
DataSourceID="SiteMapDataSource1" DynamicHorizontalOffset="2" 
Font-Names="Verdana" Font-Size="0.8em" ForeColor="#990000" 
StaticSubMenuIndent="10px" Orientation="Horizontal">
        <StaticMenuItemStyle HorizontalPadding="5px" 
VerticalPadding="2px" />
        <DynamicHoverStyle BackColor="#990000" ForeColor="White" />
        <DynamicMenuStyle BackColor="#FFFBD6" />
        <StaticSelectedStyle BackColor="#FFCC66" />
        <DynamicSelectedStyle BackColor="#FFCC66" />
        <DynamicMenuItemStyle HorizontalPadding="5px" 
VerticalPadding="2px" />
        <StaticHoverStyle BackColor="#990000" ForeColor="White" />
    </asp:Menu>
    <asp:SiteMapDataSource ID="SiteMapDataSource2" runat="server" 
ShowStartingNode="False"
        StartingNodeUrl="~/Default2.aspx" />
     </div>
    <asp:Menu ID="Menu2" runat="server" 
DataSourceID="SiteMapDataSource2"
        Orientation="Horizontal" BackColor="#F7F6F3" 
DynamicHorizontalOffset="2" Font-Names="Verdana" Font-Size="0.8em" 
ForeColor="#7C6F57" StaticSubMenuIndent="10px" 
StaticDisplayLevels="2">
        <StaticMenuItemStyle HorizontalPadding="5px" 
VerticalPadding="2px" />
        <DynamicHoverStyle BackColor="#7C6F57" ForeColor="White" />
        <DynamicMenuStyle BackColor="#F7F6F3" />
        <StaticSelectedStyle BackColor="#5D7B9D" />
        <DynamicSelectedStyle BackColor="#5D7B9D" />
        <DynamicMenuItemStyle HorizontalPadding="5px" VerticalPadding="2px" />
        <StaticHoverStyle BackColor="#7C6F57" ForeColor="White" />
    </asp:Menu>

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

    <asp:Menu ID="Menu3" runat="server" 
    BackColor="#B5C7DE" DynamicHorizontalOffset="2" 
Font-Names="Verdana" 
    Font-Size="0.8em" ForeColor="#284E98" StaticDisplayLevels="3"
    StaticSubMenuIndent="30px">
        <Items>
            <asp:MenuItem Text="Настройки" Value="Настройки" 
Selectable="false" >
                <asp:MenuItem Text="Анкета" Value="Анке-
та"></asp:MenuItem>
                <asp:MenuItem Text="Подписка" Value="Подпи-
ска"></asp:MenuItem>
                <asp:MenuItem Text="Пароль" Value="Па-
роль"></asp:MenuItem>
            </asp:MenuItem>
        </Items>
    </asp:Menu>

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

<script runat="server">
   protected void Menu1_MenuItemClick(object sender, 
MenuEventArgs e)
   {
      Listbox1.Items.Add(e.Item.Parent.Value + " : " + 
e.Item.Value);
   }
</script>

Заключение

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

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

Примечание: терминология в этой области не устоялась. В некоторых книгах используют выражение "эталонные страницы". Можно применять термин "главная страница", хотя его можно спутать с Default.aspx.

Зачем нужны шаблоны дизайна страниц?

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

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

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

Основы Master Pages

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

Начиная с этой лекции, будем разбирать проект, который Visual Studio 2005 создает по шаблону Personal Web Site Starter Kit. В нем показаны шаблоны страниц, темы и персонализация, навигация. Элементы управления навигации сосредоточены на странице шаблона Default.master. И это естественное решение, так как навигация нужна везде.

В страницу шаблона также включают общие заголовки и нижние колонтитулы.

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

Когда дочерняя страница редактируется в среде разработки, на вкладке Design видна полная страница вместе с элементами из шаблона, но они показаны серым цветом. Их редактировать нельзя. Можно редактировать то, что находится в элементах Content.

В диалоге Add New Item выберите тип страницы Master Page. Как и обычные страницы, их можно создавать с отделенным кодом или кодом, встроенным в страницу. Это никак не влияет на модель разделения кода дочерних страниц. Кроме того, главная и дочерняя страницы могут разрабатываться на разных языках.

Чтобы получить четкое разделение страницы на логические части, используют таблицы. Построим таблицу с тремя строками, где в верхней строке находится заголовок всех страниц, а во второй — произвольное содержание; она состоит из двух ячеек, в каждой из которых по одному ContentPlaceHolder. В нижнем ряду располагается правовая информация. Таблица создается очень легко из меню Layout-Insert Table. Создайте таблицу 2 на 3. После этого объедините ячейки верхней и нижней строк, а в среднюю вставьте ContentPlaceHolder. Создайте содержание заголовка и подвала. Должна получиться подобная страница:

<%@ Master Language="C#" AutoEventWireup="true" 
CodeFile="MasterPage.master.cs" Inherits="MasterPage" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Привет!</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <table>
            <tr bgcolor="#6699cc">
                <td colspan="2" style="vertical-align: middle;
    color: white;
    text-align: center;">Школа программирования ASP.NET 2.0
                </td>
            </tr>
            <tr>
                <td style="width: 100px" valign="top">
        <asp:contentplaceholder id="ContentPlaceHolder1" 
runat="server">
        </asp:contentplaceholder>
                </td>
                <td style="width: 100px" valign="top">
                    <asp:ContentPlaceHolder 
ID="ContentPlaceHolder2" runat="server">
                    </asp:ContentPlaceHolder>
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <span style="font-size: 8pt">Copyright © 
2006 - Школа ASP.NET 2.0</span></td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

Первое отличие этой страницы от обычной в том, что она начинается с директивы Master, а не Page. Класс мастер-страницы определен в файле MasterPage.master.cs:

public partial class MasterPage : System.Web.UI.MasterPage
{
    protected void Page_Load(object sender, EventArgs e)
    {
 
    }
}

Класс шаблона — наследник System.Web.UI.MasterPage, который в свою очередь наследует от System.Web.UI.UserControl.

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

Теперь нужно создать страницу содержания. Она создается как обычно, только отмечается флажок с надписью Select Master Page. Появляется диалог, в котором необходимо выбрать шаблон страницы:

 <%@ Page Language="C#" MasterPageFile="~/MasterPage.master"

AutoEventWireup="true" CodeFile="MainSchool.aspx.cs"

Inherits="MainSchool" Title="Untitled Page" %>

Атрибут MasterPage директивы Page определяет шаблон дизайна, или эталонную страницу данной страницы.

Возможно настроить приложение так, чтобы все страницы наследовали одну страницу шаблона дизайна. В конфигурационном файле в секцию System.web нужно вставить элемент:

        <pages masterPageFile="~/ MasterPage.master " />

Но и в этом случае назначение главной страницы в директиве Page имеет приоритет над назначением на уровне приложения. Установка web.config действует на тех страницах, в которых masterPageFile не указан, но определены элементы управления Content. Эта установка не действует на обычные aspx-страницы.

Шаблон можно назначить не всем страницам, а выборочно, например, расположенным в определенной папке приложения:

  <location path="Lections">

    <system.web>

      <pages masterPageFile="~/Lections.master" />

    </system.web>

Элемент location вставляется в главный узел configuration. Здесь указано, что все страницы из папки Lections используют шаблонную страницу Lections.master.

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

<asp:Content ID="Content1"

ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">

</asp:Content>

<asp:Content ID="Content2"

ContentPlaceHolderID="ContentPlaceHolder2" Runat="Server">

</asp:Content>

Программа создала дочернюю страницу с двумя элементами управления Content. Если переключиться на вкладку Design, эти два элемента Content будут показаны в виде пустых прямоугольников, расположенных рядом друг с другом, так как в шаблоне они находятся в двух соседних ячейках таблицы. Директива Page отличается от обычной, кроме атрибута MasterPageFile, наличием атрибута Title. Так как теги <head>, <title>, <body> наследуются из файла шаблона, это единственная возможность переопределить заголовок браузера. Заменим значение атрибута:

Title="Главная"

Если посмотреть на результирующую страницу в браузере, увидим, что заголовок браузера отразил изменение. В HTML-коде страницы записано

<title>

   Главная

</title>

На самой странице отображается только содержание, заданное в странице шаблона.

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

<img src=" images/ASPdotNET_logo.jpg" />

(Ее можно найти в установке .NET "Microsoft.NET\Framework\ v2.0.xxx\ASP.NETWebAdminFiles\Images)

Дочерняя страница в браузере теперь выглядит так:

http://www.intuit.ru/department/se/aspdotnet/12/12_1.png


Рис. 12.1. 

Содержание страницы должно быть строго внутри элементов Content. В один из них можно поместить, например, картинку, а во второй — текст:

<asp:Content ID="Content1"

ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">

  <asp:Image ID="Image1" runat="server" 

ImageUrl="Images/photo_home_01.jpg" />

</asp:Content>

<asp:Content ID="Content2"

ContentPlaceHolderID="ContentPlaceHolder2" Runat="Server">

  Вы решили изучить ASP.NET?<br />

Преимущество технологии ASP.NET перед остальными состоят в высокой степени абстракции, построенной над стандартным HTML-кодом: применение объектно-ориентированной парадигмы, поддержка нескольких языков программирования, наличие универсального основания, содержащего тысячи уже готовых для использования в проектах решений — Microsoft .NET Framework.

  <br />

  <asp:HyperLink ID="HyperLink1" runat="server" 

NavigateUrl="Voting.aspx">Дальше</asp:HyperLink>

</asp:Content>

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

public MasterPage Master { get; }

 

public partial class MainSchool : System.Web.UI.Page

{

    protected void Page_LoadComplete(object sender, EventArgs e)

    {

      Master.Page.Title = "Школа веб-программирования";

    }

Содержание по умолчанию

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

<asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
  <asp:SiteMapPath ID="SiteMapPath1" runat="server">
  </asp:SiteMapPath>
</asp:contentplaceholder>

На странице MainSchool.aspx он отображаться не будет, так как в ней определены оба элемента Content. Однако, если в следующей странице определен только один из элементов Contentѕ

<%@ Page Language="C#" AutoEventWireup="true"  
CodeFile="Voting.aspx.cs" 
Inherits="Voting" MasterPageFile="~/MasterPage.master" %>
<asp:Content ContentPlaceHolderID="ContentPlaceHolder2" 
runat="server">
Какой язык программирования Вы предпочитаете?<br />
    <asp:RadioButtonList ID="rblVoting" runat="server" 
DataSourceID="SqlDataSource1"
        DataTextField="variant" DataValueField="id">
    </asp:RadioButtonList><br />
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:PollsConnectionString %>"
        SelectCommand="SELECT [id], [variant] FROM [poll] ORDER 
BY [variant] "></asp:SqlDataSource>
    <br />
    <asp:Button ID="Button1" runat="server" Text="Выбрать" 
/><br />
</asp:Content>

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

Программное назначение главной страницы

В странице содержания можно переназначить ее главную страницу программно. Для этого нужно присвоить нужное значение свойству Page.MasterPageFile. Шаблон поменяется независимо от того, какой шаблон был назначен в директиве @Page. Но если попробуете проделать это в функциях Page_Load или Page_Init, получите ошибку времени выполнения.

Это свойство можно изменить только во время обработки события Page_PreInit. Событие Page_PreInit — самая ранняя фаза жизненного цикла страницы, к которой можно получить доступ. Во время события Init главная и дочерняя страница уже сливаются в одну, поэтому поздно менять шаблон. По этой причине событие Page_PreInit — единственное, в обработчике которого можно работать с главной страницей отдельно от страницы содержания:

protected void Page_PreInit(object sender, EventArgs e)
{
Page.MasterPageFile = "~/MyMasterPage.master";
}

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

Label mpLabel = (Label) Master.FindControl("masterPageLabel");
if(mpLabel != null)
{
   //Set content page title to master page control
   Title.Text = mpLabel.Text
}

Страницы шаблона могут иметь методы и свойства. Чтобы можно было обращаться к ним, нужно использовать директиву @MasterType. При этом становится доступен класс страницы шаблона дизайна через строго типизированную ссылку. Атрибут TypeName задает имя типа, а VirtualPath — путь относительно корневого каталога web-приложения к файлу шаблона:

<%@ page language="C#" masterpagefile="~/Site3.master" %>
<%@ mastertype virtualpath="~/Site3.master" %>

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

    public String FooterText {
        get { 
            return Footer.Text; 
        }
        set { 
            Footer.Text = value; 
        }
    }

Таким образом, страница разрешает доступ извне к свойствам своих элементов.

Страница содержания меняет это свойство, а элемент управления AdRotator находит с помощью FindControl:

  void Page_Load()
  {
    Master.FooterText = "This is a custom footer";
    AdRotator ad = (AdRotator)Master.FindControl("MyAdRotator");
    if (ad != null)
    {
      ad.BorderColor = System.Drawing.Color.Purple;
      ad.BorderWidth = 10;
    }
  }

Вложенные мастер-страницы

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

<%@ master language="C#" masterpagefile="~/Site4.master"%>
 
<asp:content contentplaceholderid="SectionContents" runat="serv-
er">
    <h3>Perrenials</h3>
    <asp:contentplaceholder id="FlowerText" runat="server"/>
    <br /><br />
    <asp:contentplaceholder id="FlowerPicture" runat="server"/>
</asp:content>

Страница, описывающая нарциссы, находится в разделе многолетних цветов и наследует шаблон SectionPerrenials:

<%@ page language="C#" masterpagefile="~/SectionPerrenials.master" 
%>
<asp:content id="FlowerText" ContentPlaceHolderId="FlowerText" 
runat="server">
    Daffodils bloom early in spring and welcome the growing sea-
son.
</asp:content>
<asp:content id="FlowerPicture" 
ContentPlaceHolderId="FlowerPicture" runat="server">
    <asp:image id="image1" runat=server imageurl="~/images/daf-
fodil.jpg"/>
</asp:content>

Последовательность событий

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

Событие LoadComplete было введено для того, чтобы можно было обратиться из страницы содержания к элементам главной страницы, созданным в ее Page_Load. Это нельзя сделать в обработчике Page_Load страницы содержания, так как она загружается до главной страницы.

Страницы шаблонов для конкретных платформ

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

Поэтому ASP .NET 2.0 позволяет в атрибуте MasterPageFile директивы Page определить разные страницы шаблона для конкретных контейнеров:

<%@ Page Language="VB" MasterPageFile="~/Wrox.master"
Mozilla:MasterPageFile="~/WroxMozilla.master"
Opera:MasterPageFile="~/WroxOpera.master" %>

Заключение

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

"Во всех пустяковых делах важен стиль.

Во всех серьезных делах — тоже."

Оскар Уайльд

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

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

По умолчанию стиль элементов ASP .NET очень простой — черные буквы на белом фоне. Чтобы добиться красивых дизайнерских эффектов, можно использовать те же способы, что и при дизайне HTML-страниц. Например, форматировать текст с помощью тегов <i>, <b> и так далее. Но так будет трудно сохранить единое стилевое решение на всех страницах большого сайта. Настройка шрифтов с помощью тега <font> тоже считается устаревшей. Повсеместно применяются CSS (каскадные таблицы стилей).

CSS (Cascading Style Sheets, каскадные таблицы стилей) — это набор параметров форматирования, который применяется к элементам web-страницы для управления их видом и положением.

CSS — важная составная часть тем, которые мы рассмотрим далее.

При использовании таблицы связанных стилей описание селекторов и их свойств располагается в отдельном файле, как правило, с расширением css, а для связывания документа с этим файлом применяется тег <LINK>. Если определить класс в заголовке через тег <style> или внешний файл, то стиль элемента управления легко можно поменять через свойство CssClass.

В Visual Studio 2005 есть Style Builder, с помощью которого можно легко создать желаемый стиль. С ним удобно работать даже новичкам.

Стили можно разрабатывать как для конкретного элемента, так и создавать для последующего использования. В режиме Design выберите элемент управления и из контекстного меню выберите Style.

http://www.intuit.ru/department/se/aspdotnet/13/13_01.png


Рис. 13.1. 

Перейдите в режим исходного текста и увидите, что все изменения зафиксированы в атрибуте style. Например:

style="color: navy; font-style: italic; cursor: crosshair;"

Тут определено не только, как выглядит элемент управления, но и форма курсора мыши, которое он принимает, когда находится над элементом. Это свойство можно изменить на вкладке Others.

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

<asp:BulletedList ID="BulletedList1" runat="server" 
DataSourceID="SiteMapDataSource1"
        DataTextField="Title" DataValueField="Url" Style="back-
ground-color:Cornsilk ; text-align: right;" 
DisplayMode="HyperLink" CssClass="bulletedlist" 
BackColor="Fuchsia" ForeColor="#804040">
      </asp:BulletedList>

Цвет фона списка — Cornsilk, а цвет текста определяется свойством ForeColor, так как в стиле он не определен.

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

Внешние файлы стиля

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

Для того, чтобы создать определение стиля во внешнем файле, добавьте в проект новый файл. В диалоге "Новый файл" выберите тип файла StyleSheet. По умолчанию он называется StyleSheet.css.

Элементы стиля можно определять и с помощью возможности IntelliSense. Вставьте курсор внутри фигурных скобок и нажмите на Enter. Появляется выпадающее меню со списком всех возможных атрибутов стиля.

http://www.intuit.ru/department/se/aspdotnet/13/13_02.png


Рис. 13.2. 

Выберите, например, background-color (цвет фона) и поставьте двоеточие. IntelliSense предложит список возможных значений этого атрибута. Цвета можно выбрать абсолютные либо из палитры текущей темы Windows. Не забудьте поставить в конце точку с запятой.

Стили можно определить и комбинируя несколько селекторов:

H1, H2, P, lection{
color: Olive;
}

Цвета задаются или указанием названия цвета, или его числовым значением RGB, при этом перед ним надо ставить знак #. Например, #f0fff0.

Стили можно создавать тремя способами:

В этом тоже поможет диалоговое окно (контекстное меню, пункт Add Style Rule).

http://www.intuit.ru/department/se/aspdotnet/13/13_03.png


Рис. 13.3. 

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

<link href="StyleSheet.css" rel="stylesheet" type="text/css" />

Файл можно и просто перетащить из Solution Explorer.

Например, определен стиль для класса стиля textfield:

.textfield {
   border: 1px solid #929292;
   vertical-align: middle;
   padding: 3px;
   margin: 2px 0 5px 0;
}

На страницах он применяется к элементам управления с помощью свойства CssClass:

<asp:TextBox ID="TextBox1" Runat="server" Width="200" Text="" 
CssClass="textfield" />

С помощью каскадных таблиц стилей можно не только изменять внешний вид страниц, но и создавать слои. Верстка с помощью слоев приобретает все больше поклонников. Слой — это элемент <DIV> или <SPAN>, к которому добавляются параметры для изменения абсолютного или относительного положения на странице. Площадь, занимаемая разными слоями, может перекрываться. Параметр z-index определяет порядок наложения слоев. Чтобы создать слой, выберите в меню Layout-Insert Layer. В слой можно помещать элементы управления и HTML-код.

В этом примере с помощью определения стиля создается элемент GridView с фиксированным заголовком. Иногда удобно не переходить со страницы на страницу, а прокручивать записи. Вспомним, что элемент управления Panel позволял это делать. Поэтому попробуем использовать GridView и Panel совместно:

<style type="text/css">
.TableHeader
{
    border-width: 2px;
    border-color: Black;
    background-color: #990000;
    color: white;
    font-weight: bold;
    position: relative;
    top: 
expression(this.parentNode.parentNode.parentNode.scrollTop-1);
 
}
</style>

Вставьте в форму следующий код:

<br />
<asp:Panel ID="Panel1" runat="server" Height="327px" 
Width="566px" ScrollBars="Auto">
  <asp:GridView ID="GridView1" runat="server" 
AutoGenerateColumns="True" DataSourceID="SqlDataSource1"
    EmptyDataText="There are no data records to display." 
Height="165px" Width="548px">
    <HeaderStyle CssClass="TableHeader" />
  </asp:GridView>
</asp:Panel>
<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:pubsConnectionString1 %>"
  ProviderName="<%$ 
ConnectionStrings:pubsConnectionString1.ProviderName %>" 
SelectCommand="SELECT * FROM [authors]">
</asp:SqlDataSource>

У элемента Panel1 устанавливается свойство ScrollBars="Auto" и в него помещается GridView. Стиль, который применен для заголовка, задает относительное положение данного класса у верхней границы панели. При генерации HTML-кода для заголовка GridView генерируются теги <th>, которые вложены в <tr>, вложенные свою очередь в <table>. <table> находится в <div>, в который отображается Panel1. Следовательно, чтобы добраться до этого тега, надо 3 раза обратиться к родителю.

Темы и шкурки

В интерфейсе почтовой службы Rambler дизайн страниц меняется в зависимости от времени года. Пользователь может сам выбрать себе вариант дизайна. Подобной функциональности в ASP .NET можно добиться с помощи тем.

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

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

Файлы тем находятся в папке с зарезервированным названием App_Themes. Эту папку можно создать, если в контекстном меню проекта выбрать "Add ASP .NET Folder". В папке App_Themes можно создавать темы, например, для разных времен года. У каждой темы будет свой каталог, в котором будут находиться относящиеся к ней файлы. На многих сайтах пользователь может выбрать тему. Вложение тем друг в друга не допускается.

Каждая тема обычно состоит из одного или нескольких файлов скинов с расширением ".skin", а также других, необходимых для задания внешнего вида сайта файлов, таких как файлы каскадных таблиц стилей, картинок, XSL-преобразований и так далее, которые также могут быть упорядочены в поддиректориях корневой директории темы. Файлы скинов и таблиц стилей обычно расположены в корне темы, а картинки — в поддиректории Images.

Темы можно применить к странице с помощью атрибута Theme директивы Page. Тему можно поменять программно. Поэтому можно дать возможность пользователю выбрать тему. Тему можно установить до или во время события PreInit:

    protected void Page_PreInit(object sender, EventArgs e)
    {
        Page.Theme = "Black";
    }

Тему можно применить ко всем страницам приложения, если в файле web.config вставить эту директиву:

<configuration>
<system.web>
<pages theme=" Black " />
</system.web>
</configuration>

Если тема установлена в странице, она имеет преимущество перед глобальной темой. Темы страницы переопределяют свойства элементов управления. Если нужно, чтобы темы не применялись к элементу, нужно установить его свойство EnableTheming в False.

Если нужно отменить применение темы к группе элементов, можно поместить их в Panel и установить его свойство EnableTheming в False.

Свойство EnableTheming можно менять и на уровне страницы:

<%@ Page Language="C#" AutoEventWireup="true" EnableTheming="False"%>

Атрибут StylesheetTheme работает так же, как и Theme. Но если тема установлена с помощью этого атрибута, то свойства управления элемента имеют преимущество перед темой страницы.

В файлах тем с расширением .skin хранятся варианты внешнего вида элементов управления. Перевод термина skin на русский язык не устоялся. Можно называть его "шкурка" или "оболочка".

Скин — это шаблон элемента управления с определением набора визуальных свойств, которые будут использоваться для генерации элементов управления данной темы. Скины могут работать вместе с картинками и таблицами стилей. Один ".skin"-файл может хранить множество разных элементов управления. Например, в проекте "Starter Kit" определены две темы — Black и White. В файле Default.skin обеих тем хранятся скины элементов ImageButton, Image, GridView.

Можно создать столько файлов шкурок, сколько необходимо. Для удобства и ясности можно создать файлы скинов для каждого элемента, например, Label.skin, GridView.skin.

Создайте в папке App_Themes тему Summer. Добавьте в тему скин Calendar.skin.

Описание стиля элемента управления похоже на описание на странице, с тем отличием, что атрибут ControlId не указывается. Однако необходимо указать атрибут runat="server":

<asp:Calendar runat="server" BackColor="Honeydew" 
BorderColor="Teal" BorderWidth="1px" CellPadding="1" 
DayNameFormat="Shortest" Font-Names="Verdana" Font-Size="8pt" 
ForeColor="DarkSlateGray" Height="200px" Width="220px"> 
<SelectedDayStyle BackColor="#009999" Font-Bold="True" 
ForeColor="#CCFF99" /> <SelectorStyle BackColor="#99CCCC" 
ForeColor="#336666" /> <WeekendDayStyle BackColor="#C0FFC0" /> 
<OtherMonthDayStyle ForeColor="#999999" /> 
<TodayDayStyle BackColor="#99CCCC" ForeColor="White" /> 
<NextPrevStyle Font-Size="8pt" ForeColor="#CCCCFF" /> 
<DayHeaderStyle BackColor="#80FF80" 
ForeColor="#336666" Height="1px" /> 
<TitleStyle BackColor="Green" BorderColor="#3366CC" 
BorderWidth="1px" Font-Bold="True" Font-Size="10pt" 
ForeColor="White" Height="25px" /> </asp:Calendar>

Теперь на всех страницах, где установлена тема Summer, календарь будет выглядеть так:

http://www.intuit.ru/department/se/aspdotnet/13/13_04.png


Рис. 13.4. 

У описания шкурки может быть описан атрибут SkinId. Скин с установленным атрибутом SkinID называется именованным скином. Этот атрибут при описании каждого типа элемента управления должен быть уникальным. Скин применяется к тем элементам, у которых значение свойства SkinId совпадает со SkinId описания:

<asp:Label Runat="server" SkinId="June" ForeColor="#Teal" Font-Names="Verdana"
Font-Size="X-Small" />

При этом, если у элемента не определен SkinId, применяется скин, в котором SkinId тоже отсутствует (скин по умолчанию). Этот скин для каждого класса может быть описан только один раз.

Определим еще один именованный скин календаря:

<asp:Calendar runat="server" SkinId="June" BackColor="#C0FFC0" 
BorderColor="Teal" BorderWidth="1px" CellPadding="1" 
DayNameFormat="Shortest" Font-Names="Verdana" Font-Size="8pt" 
ForeColor="DarkSlateGray" Height="200px" Width="220px"> 
<SelectedDayStyle BackColor="#009999" Font-Bold="True" 
ForeColor="#CCFF99" /> <SelectorStyle BackColor="#99CCCC" 
ForeColor="#336666" /> <WeekendDayStyle BackColor="#C0FFC0" /> 
<OtherMonthDayStyle ForeColor="#999999" /> 
<TodayDayStyle BackColor="#99CCCC" ForeColor="White" /> 
<NextPrevStyle Font-Size="8pt" ForeColor="#CCCCFF" />
<DayHeaderStyle BackColor="#C0FFC0" ForeColor="#FF8000"  
Height="1px" /> 
<TitleStyle BackColor="Green" BorderColor="#3366CC" 
BorderWidth="1px" Font-Bold="True" Font-Size="10pt" 
ForeColor="White" Height="25px" /> </asp:Calendar>

При отображении приведенной ниже страницы в браузере пользователя, первый

календарь будет отображаться с использованием именованного скина June, а второй — используя общий скин по умолчанию:

    <form id="form1" runat="server">
    <div>
      <table>
        <tr>
          <td style="width: 100px">
      <asp:Calendar ID="Calendar1" runat="server" 
VisibleDate="2006-06-01" SkinID="June"></asp:Calendar>
          </td>
          <td style="width: 100px">
      <asp:Calendar ID="Calendar2" runat="server" 
VisibleDate="2006-07-01"></asp:Calendar>
          </td>
        </tr>
      </table>
    </div>
    </form>

Некоторые свойства элементов управления не могут быть изменены в файлах скинов. Например, такие свойства, как ID и EnableViewState, помечены как запрещенные к установке в файлах скинов. Также не допускаются к установке через скины такие атрибуты, как CommandName класса Button, AllowPaging, DataSource класса GridView и т.д. Словом, если свойство влияет не на внешний вид, а на поведение элемента управления, или строго индивидуально, — скорее всего его нельзя будет изменить. Это определяется разработчиком элемента управления с помощью атрибута ThemeableAttribute. Этим атрибутом можно пользоваться и создавая собственные элементы управления. Атрибут ThemeableAttribute может быть применен и к классу для обозначения поддержки данным контролом настройки своих параметров через скины. Например, класс Control определен с атрибутом Themeable Attribute=False, и все классы — непосредственные наследники Control не допускают настройки свойств через файлы .skin. Это классы Literal, Repeater, MultiView, Xml и другие. Хотя класс WebControl наследует Control, он помечен атрибутом Themeable=True, и это распространяется и на его наследников, каковым являются большинство элементов управления.

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

body
{
   font-size: x-small;
   font-family: Verdana;
   color: #004000;
   background-color: #F0FFF0;
}

Установка стиля в файле css относится ко всему тексту страницы, в том числе к элементам Repeater и Literal. При этом если в каком-нибудь элементе явно установить шрифт, эти установки переопределят установки из css.

В папки с темами можно включать картинки. Их используют в файлах скинов. В приложении Starter Kit картинки задают внешний вид кнопок:

 <asp:ImageButton Runat="server" ImageUrl="Images/button-
import.gif" SkinID="import" />
<asp:imagebutton runat="server" Imageurl="Images/button-login.gif"
skinid="login" />

Программная работа с темами

Как и шаблоны, темы можно назначать не позднее события PreInit:

protected void Page_PreInit(object sender, System.EventArgs e)
{
Page.Theme = Request.QueryString["ThemeChange"];
}

Заключение

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

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

Мы познакомились с большим количеством встроенных в ASP.NET элементов управления.

Хотя набор стандартных элементов велик — всегда может понадобиться такой элемент, которого в стандартной поставке нет. Или есть страница с такой функциональностью, которую хочется использовать и на других страницах. Можно, конечно воспользоваться клеем и ножницами (Copy-Paste), но сущность объектно-ориентированного и компонентного программирования — в повторном использовании кода, заключенного в готовые компоненты. Следовательно, нужно научиться создавать собственные элементы управления.

Пользовательские элементы управления инкапсулируют несколько готовых элементов в одном контейнере, который можно повторно использовать в проекте. Они наследуются от класса UserControl — наследника класса Control. Пользовательские элементы компилируются точно так же, как и страницы aspx.

Серверные элементы, кроме этого, реализуют собственное поведение и самостоятельно выводят HTML-код, который их отображает. Они могут наследоваться от WebControl или одного из классов стандартных элементов управления. Их можно использовать в любых проектах и распространять в виде откомпилированной PE (Portable Executable) сборки.

Мы знаем, что класс Page наследует класс Control, как и все элементы управления, некоторые прямо, а другие через класс WebControl или HtmlControl. Следовательно, между написанием страницы и разработкой собственного элемента управления должно быть много общего. У них тоже есть свой жизненный цикл. В классе Control определены события Init, Load, DataBinding, PreRender, Unload, Disposed. Свойства, которые Control предоставляет своим наследникам, включают EnableViewState, ID, UniqueID, Page, Parent, SkinID, ViewState и Controls — коллекция дочерних элементов управления.

Класс Control предоставляет возможность помещать элемент управления в дерево элементов управления, которые отображаются на странице .aspx. Класс Control также реализует интерфейс System.ComponentModel. IComponent, который делает компонент конструктивным. Конструктивный компонент может быть добавлен в панель Toolboox визуального дизайнера, может быть помещен на разрабатываемую страницу методом drag-and-drop, может отображать свойства в окне свойств и обеспечивать другие виды поддержки режима разработки (в том числе Smart Tags).

Пользовательские элементы управления можно создавать в визуальном редакторе по той же модели, что и страницы aspx. Как всегда, откроем диалог NewFile и выберем тип страницы Web User Control. Расширение файла с дизайном элемента — ascx, а с кодом класса — ascx.cs. В отличие от страниц aspx, сам по себе пользовательский элемент нельзя увидеть в браузере, для этого он должен находиться на какой-нибудь странице:

<%@ Control Language="C#" AutoEventWireup="true" 
CodeFile="WebUserControl.ascx.cs" Inherits="WebUserControl" %>

Как упоминалось в лекции 2, директива Control — это аналог директивы Page для элемента управления. На странице не указываются теги <html> и <body>, потому что содержание элемента включается в код страницы, в котором он содержится. Изначально в элементе вообще нет никаких тегов.

Класс пользовательского элемента управления — наследник System.Web.UI.UserControl. В остальном он ничем не отличается от файла с классом страницы:

public partial class WebUserControl : System.Web.UI.UserControl
{
}

Можно добавить в него любые элементы управления и HTML-код:

    <h1><%= Greeting %>, <%= Name %>!</h1>
<asp:TextBox ID="txtName" runat="server"></asp:TextBox><br />
<asp:Button ID="btnClick" runat="server" Text="Button" />

В классе элемента управления определим его свойства:

    string name;
    string greeting;
    public string Greeting
    {
        get
        {
            return greeting;
        }
 
        set
        {
            greeting = value;
        }
    }
    public string Name
    {
        get
        {
            return name;
        }
 
        set
        {
            name = value;
        }
    }
 
  protected void Page_Init(object sender, EventArgs e)
  {
    btnClick.Text = "Enter your name and click";
  }

При нажатии на кнопку свойства элемента заполняются данными из текстового поля:

  protected void btnClick_Click(object sender, EventArgs e)
  {
    Name = txtName.Text;
  }

Теперь перетащите название элемента из Solution Explorer на любую страницу.

Чтобы использовать пользовательский элемент на странице, его надо зарегистрировать. Директива Register появляется автоматически:

<%@ Register Src=" WebUserControl.ascx" TagPrefix="User" 
TagName="GreetingControl" %>

Атрибут TagPrefix директивы Register задает префикс, с помощью которого данный пользовательский элемент можно создавать на страницах aspx. Его значение может быть любым, кроме asp, которое зарезервировано для встроенных элементов управления ASP .NET. TagName — это имя элемента, идущее после префикса; атрибут Src определяет путь к файлу пользовательского элемента управления.

Теперь новый пользовательский элемент управления можно описать так:

<User:GreetingControl id="Hello" runat="server" Name="Heinrich"
     Greeting="Guten Tag"/>

Свойства, описанные в классе, можно устанавливать в описании на странице, причем они даже будут видны в окне свойств дизайнера.

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

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

  protected void Page_Load(object sender, EventArgs e)
  {
    Hello.Greeting = "Привет";
  }

Пользовательский элемент может получить доступ к странице, в которой находится, через свойство Parent. Если в родительскую форму добавить TextBox, он может прочитать его значение и использовать его:

    <asp:TextBox ID="txtGreeting" 
runat="server"></asp:TextBox><br />
      <User:GreetingControl id="Hello2" runat="server" 
Name="Heinrich"
     Greeting="Guten Tag" OnLoad="Hello2_Load"/>
 
  protected void btnClick_Click(object sender, EventArgs e)
  {
    Name = txtName.Text;
    TextBox tb=Parent.FindControl("txtGreeting") as TextBox;
    if ( tb!= null)
    {
      Greeting = tb.Text;
    }
  }

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

Можно попробовать создать WebUserControl в событии Page_Load, но это не даст результата. Причина в том, что класс объявлен лишь частично в файле отделенного кода, в нем не хватает функции отрисовки, которая появится при обработке страницы ascx ASP .NET.

Чтобы программно создать экземпляр пользовательского элемента управления, нужно вызвать функцию LoadControl, который вернет экземпляр класса System.Web.UI.Control, содержащий загружаемый элемент управления. Чтобы иметь доступ к свойствам класса WebUserControl, нужно преобразование типа. И, как и всякий другой элемент управления, загруженный пользовательский элемент управления может быть добавлен в коллекцию элементов управления web-формы. Его нельзя добавить в страницу, так как его составной частью является кнопка, которая может находиться только в форме:

    WebUserControl wuc = 
(WebUserControl)Page.LoadControl("WebUserControl.ascx");
    wuc.Greeting = "Здоровеньки булы";
    wuc.Name = "Тарас";
    form1.Controls.AddAt(0, wuc);

Более полезный пользовательский элемент управления — нижний колонтитул любой страницы. Его можно поместить на шаблон дизайна. Он может отобразить юридическую информацию, адрес web-мастера и дату последнего обновления:

<%@ Control Language="C#" AutoEventWireup="true" 
CodeFile="Footerl.ascx.cs" Inherits="Footerl" %>
Copyright &copy; <asp:Label ID="lblYear"
runat="server" /> by Your Company Name.<br />
 
Замечания, комментарии, проблемы? Свяжитесь с web-мастером 
<asp:Label ID="lblEmail" runat="server" /><br />
Дата последней модификации этой страницы: <asp:Label 
ID="lblLastMod" runat="server" /><br />

Текст в элементах Label меняется в обработчике Page_Load.

  protected void Page_Load(object sender, EventArgs e)
  {
    lblYear.Text = DateTime.Now.Year.ToString();
    lblEmail.Text = "<a href='mailto:webmaster@"
       + Request.Url.Host.Replace("www.", "") + 
"'>webmaster</a>";
    lblLastMod.Text = 
System.IO.File.GetLastWriteTime(Server.MapPath(Request.Url.LocalPa
th)).ToLongDateString();
  }

Серверные элементы управления

Серверные элементы управления, унаследованные от WebControl или Control, сложнее создавать, но у них еще больше возможностей. Такие элементы имеют собственные методы генерации HTML-кода. Сложность в том, что здесь класс полностью описывается программистом, без визуального дизайна и файла ascx. В классе WebControl определены визуальные свойства, такие как BackColor, Font, ToolTip. В них можно определить сложную логику пользовательского интерфейса. Если элемент управления не нуждается в таких свойствах, его нужно наследовать от Control. При этом он генерирует HTML-код, например теги <meta> или скрытые элементы.

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

Серверные элементы управления помещаются в библиотеки WebControls. Чтобы создать библиотеку, в меню File выберите New Project, и в появившемся диалоге — тип проекта Web Control Library (он находится в узле Visual C#-Windows). В проекте уже создан простейший серверный элемент управления. Библиотеку можно создать только в Visual Studio, а в VWD — только класс в папке App_Code:

namespace WebControlLibrary1
{
  [DefaultProperty("Text")]
  [ToolboxData("<{0}:WebCustomControl1 
runat=server></{0}:WebCustomControl1>")]
  public class WebCustomControl1 : WebControl
  {
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("")]
    [Localizable(true)]
    public string Text
    {
      get
      {
        String s = (String)ViewState["Text"];
        return ((s == null) ? String.Empty : s);
      }
 
      set
      {
        ViewState["Text"] = value;
      }
    }
 
    protected override void RenderContents(HtmlTextWriter output)
    {
      output.Write(Text);
    }
  }
}

У этого элемента всего одно свойство Text, и он просто записывает в поток вывода страницы HTML значение этого свойства.

Если в решении есть проект с библиотекой пользовательских элементов, они автоматически добавляются в инструментальную панель (Toolbox). Для этого достаточно всего лишь скомпилировать проект. В папке Bin появляется WebControlLibrary1.dll. Это сборка, в которой находятся все элементы управления библиотеки.

Если вы работаете не с Visual Studio, все равно можно откомпилировать классы в сборку .dll из командной строки1).

csc /t:library /out: WebControlLibrary1.dll /r:System.dll 
/r:System.Web.dll *.cs

В панели инструментов появится новая секция со значками-шестеренками, и элементы управления можно перетаскивать оттуда на страницы.

Директива Register, которая автоматически добавляется, будет содержать название этой сборки и пространство имен, в котором находится элемент управления:

<%@ Register Assembly="WebControlLibrary1" 
Namespace="WebControlLibrary1" TagPrefix="cc1" %>

Чтобы не писать одну и ту же директиву на многих страницах, библиотеку можно зарегистрировать в файле web.config.

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

Доступ к сборке WebControlLibrary1.dll можно предоставить всем приложениям, если поместить ее в глобальный кэш сборок.

Атрибуты

Наличие атрибутов — важное свойство языков .NET. С их помощью в метаданные класса в сборку добавляется информация, которая используется самым различным образом. Атрибуты — это классы, наследующие System.Attribute и применяющиеся к пространствам имен, классам, свойствам и методам. Синтаксис применения атрибута в C#:

[CustomAttr(Update:=true, Keep=false)]

Конструктор атрибута указывается в квадратных скобках с параметрами, которые определены в декларации класса атрибута.

В ASP .NET атрибуты в том числе определяют поведение пользовательских элементов управления (а также, например, используются в описании web-сервисов). В описании WebCustomControl1 атрибуты применены и к самому классу, и к свойству Text. Атрибуты класса определяют его помещение в панель Toolbox и в дизайнере страниц. Их можно поделить на 3 категории: атрибуты, помогающие среде разработки работать с элементом управления в режиме дизайна; атрибуты, управляющие выводом дочерних элементов; атрибуты, определяющие его поведение в панели инструментов.

DefaultProperty определяет свойство по умолчанию. При открытии окна свойств элемента управления в дизайнере помеченное этим атрибутом свойство будет выделено бежевым.

ToolboxData задает форматирующую строку. Например,

ToolboxData("<{0}:WebCustomControl1 
runat=server></{0}:WebCustomControl1>")

Это строка с формальным параметром, в качестве значения которого подставляется атрибут TagPrefix директивы Register, регистрирующей данный элемент на конкретной странице.

Когда в дизайнере страницы происходит двойной щелчок мыши на элементе, среда разработки создает обработчик события, например, SelectedIndexChanged для выпадающего списка DropDownList. Какое именно событие, определяется атрибутом DefaultEvent.

Атрибуты применяются также к свойствам и событиям элемента.

Bindable указывает, что свойство можно связать с источником данных.

Category обозначает категорию в окне свойств элемента. Свойства разбиваются на категории, если выбрать представление Categorized окна свойств.

Атрибут Themable определяет, может ли данное свойство определяться в файле скина.

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

    [Themeable(false)]
    public string Text

Browsable указывает, будет ли отображаться свойство в окне свойств, и EditorBrowsable — возможно ли будет его редактировать.

Существуют и другие атрибуты.

Отрисовка (Rendering) элемента управления

В этом примере построен элемент управления, наследующий от Label. Он раскрашивает текст в случайные цвета. Свойство EnableRainbowMode можно отключить, тогда он станет вести себя как обычная метка:

  [ToolboxData("<{0}:RainbowLabel 
runat=server></{0}:RainbowLabel>")]
  public class RainbowLabel : Label
  {
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("true")]
    [Localizable(true)]
    public bool EnableRainbowMode
    {
      get
      {
        if (ViewState["EnableRainbowMode"] == null)
          return true;
        else
          return 
bool.Parse(ViewState["EnableRainbowMode"].ToString());
      }
      set
      {
        ViewState["EnableRainbowMode"] = value;
      }
    }
 
    protected override void RenderContents(HtmlTextWriter output)
    {
      if(EnableRainbowMode)
        output.Write(ColorizeString(Text));
      else
        output.Write(Text);
    }
    private string ColorizeString(string input)
    {
      StringBuilder output = new StringBuilder(input.Length);
      Random rand = new Random(DateTime.Now.Millisecond);
 
      for (int i = 0; i < input.Length; i++)
      {
        int color = rand.Next(0xffffff);
        string strColor = string.Format(@"<span style=""color: 
#{0:x}"">", color);
        output.Append(strColor);
        output.Append(input.Substring(i, 1));
        output.Append("</span>");
      }
      return output.ToString();
    }
  }

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

Составные элементы управления наследуются от класса Composite Control. Этот элемент представляет собой объединение текстовой строки с валидатором, который проверяет ее значение на соответствие шаблону адреса электронной почты. EnsureChildControls — это метод, который проверяет, существуют ли вложенные элементы. Если нет, вызывается метод CreateChildControl: [DefaultProperty("Text")]

  [ToolboxData("<{0}:EmailTextBox 
runat=server></{0}:EmailTextBox>")]
  public class EmailTextBox : CompositeControl, INamingContainer
  {
    private TextBox textBox;
    private RegularExpressionValidator validator;
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("")]
    [Localizable(true)]
    [Themeable(false)]
    public string Text
    {
      get
      {
        EnsureChildControls();
        return textBox.Text;
      }
      set
      {
        EnsureChildControls();
        textBox.Text = value;
      }
    }
    [Themeable(false)]
    public string ErrorMessage
    {
      get
      {
        EnsureChildControls();
        return validator.ErrorMessage;
      }
      set
      {
        EnsureChildControls();
        validator.ErrorMessage = value;
      }
    }
    public override ControlCollection Controls
    {
      get
      {
        EnsureChildControls();
        return base.Controls;
      }
    }
    protected override void CreateChildControls()
    {
      Controls.Clear();
 
      textBox = new TextBox();
      validator = new RegularExpressionValidator();
 
      Controls.Add(validator);
      Controls.Add(textBox);
 
      textBox.ID = "Email1";
      validator.ControlToValidate = textBox.ID;
      validator.ValidationExpression=@"\w+([-+.']\w+)*@\w+([
-.]\w+)*\.\w+([-.]\w+)*";
    }
  }

У элемента управления EmailTextBox имеются свойства Text и ErrorMessage, которые можно определять на страницах aspx.

      <cc1:EmailTextBox ID="EmailTextBox1" runat="server"  
Text="Hello" ErrorMessage="Адрес E-mail неправильный!"/>

Заключение

Пользовательские и собственные серверные элементы управления — это реализация в ASP .NET передовой концепции компонентного программирования. Они облегчают повторное использование кода.

В каждом проекте существуют файлы, в которых содержится информация, относящаяся ко всему сайту. Это файл конфигурации web.config и файл global.asax.

Файл Web.config

ASP .NET конфигурируется с помощью нескольких глобальных файлов .config, которые находятся в директории .NET Framework. Это файлы формата XML, которые позволяют легко изменить поведение ASP .NET. Например, там находятся machine.config и machine.config.comments, в которых содержатся настройки сервера.

Например, в узле DbProviderFactories файла machine.config находится список провайдеров баз данных. Эти провайдеры появляются в диалоге Add Connection при добавлении новых соединений с источниками данных. В список можно добавлять новых провайдеров:

    <DbProviderFactories>
      <add name="Odbc Data Provider" invariant="System.Data.Odbc" 
description=".Net Framework Data Provider for Odbc" 
type="System.Data.Odbc.OdbcFactory, System.Data, Version=2.0.0.0, 
Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add name="OleDb Data Provider" 
invariant="System.Data.OleDb" description=".Net Framework Data 
Provider for OleDb" type="System.Data.OleDb.OleDbFactory, 
System.Data, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=b77a5c561934e089" />
      <add name="OracleClient Data Provider" 
invariant="System.Data.OracleClient" description=".Net Framework 
Data Provider for Oracle" 
type="System.Data.OracleClient.OracleClientFactory, 
System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=b77a5c561934e089" />
      <add name="SqlClient Data Provider" 
invariant="System.Data.SqlClient" description=".Net Framework Data 
Provider for SqlServer" 
type="System.Data.SqlClient.SqlClientFactory, System.Data, 
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
/>
      <add name="SQL Server CE Data Provider" 
invariant="Microsoft.SqlServerCe.Client" description=".NET 
Framework Data Provider for Microsoft SQL Server 2005 Mobile 
Edition" type="Microsoft.SqlServerCe.Client.SqlCeClientFactory, 
Microsoft.SqlServerCe.Client, Version=9.0.242.0, Culture=neutral, 
PublicKeyToken=89845dcd8080cc91" />
    </DbProviderFactories>

В файле machine.config содержится информация, необходимая для работы служб персонализации и управления ролями. Профили пользователей хранятся в aspnetdb.mdf локального сервера SQL Express:

<connectionStrings>
    <add name="LocalSqlServer" connectionString="data 
source=.\SQLEXPRESS;Integrated 
Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User 
Instance=true" providerName="System.Data.SqlClient" />
  </connectionStrings>

Каждая страница ASP .NET по умолчанию может обращаться к некоторым пространствам имен. Список этих пространств имен определен в конфигурационном файле <windir>\Microsoft.NET\Framework\<version>\CONFIG\web.config:

    <pages>
        <namespaces>
            <add namespace="System" />
            <add namespace="System.Collections" />
            <add namespace="System.Collections.Specialized" />
            <add namespace="System.Configuration" />
            <add namespace="System.Text" />
            <add namespace="System.Text.RegularExpressions" />
            <add namespace="System.Web" />
            <add namespace="System.Web.Caching" />
            <add namespace="System.Web.SessionState" />
            <add namespace="System.Web.Security" />
            <add namespace="System.Web.Profile" />
            <add namespace="System.Web.UI" />
            <add namespace="System.Web.UI.WebControls" />
            <add namespace="System.Web.UI.WebControls.WebParts" />
            <add namespace="System.Web.UI.HtmlControls" />
       </namespaces>

Вы можете изменять этот список, например добавить те пространства имен, которые используете во всех проектах. Но будьте осторожны, потому что в разных пространствах имен определены классы с одинаковыми именами. Например, класс Label определен в System.Windows.Forms и в System.Web.UI.WebControls, но в ASP .NET нужен только второй. Использование класса Label, когда включены оба заголовка, приведет к ошибке компиляции.

Классы находятся в сборках (assembly), и если вы хотите использовать нестандартную сборку, в проекте нужно создать ссылку на нее. Например, при работе с базой данных Oracle включается сборка System.Data.OracleClient. Чтобы создать ссылку, выберите в меню Website пункт Add Reference и выберите нужный компонент. В файл web.config проекта добавится пункт

<assemblies>
<add assembly="System.Data.OracleClient, Version=2.0.0.0, 
Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
</assemblies>

Какие сборки включаются по умолчанию, также определено в конфигурационном файле .NET Framework web.config:

        <compilation>
   <add assembly="mscorlib" />
   <add assembly="System, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=b77a5c561934e089" />
   <add assembly="System.Configuration, Version=2.0.0.0, 
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
   <add assembly="System.Web, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=b03f5f7f11d50a3a" />
   <add assembly="System.Data, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=b77a5c561934e089" />
   <add assembly="System.Web.Services, Version=2.0.0.0, 
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
   <add assembly="System.Xml, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=b77a5c561934e089" />
   <add assembly="System.Drawing, Version=2.0.0.0, Culture=neu-
tral, PublicKeyToken=b03f5f7f11d50a3a" />
   <add assembly="System.EnterpriseServices, Version=2.0.0.0, 
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
   <add assembly="System.Web.Mobile, Version=2.0.0.0, 
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
   <add assembly="*" />
</assemblies>

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

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

Корневой узел Web.config называется configuration:

<configuration

xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

В нем содержится информация трех видов:

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

Они находятся в узле appSettings:

<configuration>

<appSettings>

<add key="SalesTax" value="0.08" />

</appSettings>

При этом получить доступ к настройкам из приложения можно с помощью свойства AppSettings:

ConfigurationManager.AppSettings["SalesTax"];

2. Строки соединения с источниками данных. Примеры были приведены в лекции 7.

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

using System.Configuration;

 

  ConnectionStringSettings connectionStringSettings =

ConfigurationManager.ConnectionStrings["NorthwindConnectionString"

];

ConnectionStringSettings наследует Класс ConfigurationElement.

3. Установки System.Web и System.Net

В секции System.Net хранятся установки почтового сервера, если он есть.

Настройки System.Web состоят из нескольких категорий:

<pages styleSheetTheme="White"/>

<authentication mode="Forms">

<forms loginUrl="Default.aspx" protection="Validation" time-

out="300"/>

</authentication>

<globalization requestEncoding="utf-8" responseEncoding="utf-8"/>

<roleManager enabled="true"/>

  <customErrors mode="RemoteOnly">

    <error statusCode="404" redirect="missingPage.aspx"/>

  </customErrors>

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

Атрибут mode="RemoteOnly" определяет, что эту страницу увидят только пользователи, а администратор сайта увидит стандартное сообщение об ошибке.

<compilation debug="true"/>

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

Это лишь некоторые из доступных настроек. В файле <windir>\ Microsoft.NET\Framework\<version>\CONFIG \web.config.comments находится подробное описание всех возможных настроек web.config.

Изменять настройки web.config можно двумя способами. Первый — вручную редактировать его текст в редакторе. И второй — с помощью web-интерфейса, который работает на локальном сервере. Для этого из меню Website выберите пункт ASP .NET Configuration или нажмите на крайнюю справа кнопку в окне Solution Explorer.

Web-приложение и сессия, обработка глобальных событий

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

Однако известно, что протокол HTTP изначально не поддерживает сессии. Обычно сервер посылает страницу в ответ на запрос, и на этом соединение обрывается. Хотя HTTP 1.1 поддерживает режим keep-alive, сервер не в состоянии определить, что запросы идут с одного и того же клиента. В ASP .NET при каждом соединении на сервере создается сессия, идентификатор которой обычно хранится в файле-cookie (или передается в командной строке, если это невозможно).

До сих пор мы рассматривали работу ASP .NET в пределах одной страницы. Нажали кнопку — получили результат. На практике при работе с web-приложениями к цели ведут десятки взаимосвязанных запросов, делающихся на разных страницах. Что объединяет страницы приложения в одно целое?

В ASP .NET есть специальный класс — HttpApplication, представляющий все приложение. Он контролирует его общее состояние и обрабатывает глобальные события.

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

Также в ASP.NET присутствует другой тип окружения — сессия (объект Session класса HttpSessionState). Сессия объединяет серию запросов с одного адреса в течение некоторого времени. В пределах сессии можно контролировать текущего пользователя, так что именно в сессии удобно отслеживать последовательность его действий. В сессии можно хранить данные, полученные из разных источников, которые относятся к пользователю, или даже объекты классов приложения.

Текущая сессия и приложение доступны с любой страницы через свойства Session и Application, так что получить доступ к ним очень просто.

Параметры сессии также доступны в виде словаря через индексатор со строковым параметром. Область видимости переменной сеанса — весь сеанс взаимодействия посетителя с приложением в конкретном окне браузера. Сеансом является период времени, когда пользователь находится на сайте. Он начинается, когда посетитель впервые заходит на сайт. Пользователь может закрыть браузер, и сервер не будет знать об этом. Поэтому в сессии существует таймаут, который по умолчанию равен 20 минутам. Если в течение этого времени пользователь не совершал активных действий на сайте, сессия считается закрытой. Для приложений, где потеря данных критична (например, финансовых), обычно таймаут уменьшают.

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

Так добавляется переменная сервера:

Session.Add("Username", "Tom");
Session["Username"]= "Tom";

Эти присваивания равносильны.

В сессии могут храниться переменные любых типов, в том числе пользовательские.

Запрос к данным сессии возвращает тип object, поэтому после получения переменной необходимо привести его к нужному типу:

Username= Session["Username"].ToString();

Таймаут страницы можно поменять:

Session.TimeOut = 5;

Явно завершить сеанс позволяет метод Abandon:

Session.Abandon();

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

Application["Name"] = "Myname"

Файл Global.asax, объекты приложения и сеанса

Еще одним глобальным понятием является обработка событий уровня приложения. Это события вроде: "на одной из страниц приложения началась обработка запроса" или "на какой-то странице произошла ошибка". Такие события обрабатываются в коде файла global.asax.

Добавьте в проект новый файл типа Global Application Class. Это можно сделать через меню Website, или щелкнув правой кнопкой мыши на названии проекта в Visual Studio, или через меню File —> New в WebMatrix.

http://www.intuit.ru/department/se/aspdotnet/15/15_01sm.png


увеличить изображение
Рис. 15.1. 

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

События для файла Global.asax

Событие

Условия наступления

Application_Start

Первая страница приложения открывается любым пользователем

Application_End

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

Session_Start

Посетитель активизирует приложение

Session_End

Пользователь покидает приложение или не запрашивает страницу в течение некоторого периода времени

Application_Error

При выполнении приложения возникает необработанная ошибка

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

<%@ Application Language="C#" %>
 
<script runat="server">
 
    void Application_Start(object sender, EventArgs e) 
    {
        // Code that runs on application startup
 
    }
    
    void Application_End(object sender, EventArgs e) 
    {
        //  Code that runs on application shutdown
 
    }
        
    void Application_Error(object sender, EventArgs e) 
    { 
        // Code that runs when an unhandled error occurs
 
    }
 
    void Session_Start(object sender, EventArgs e) 
    {
        // Code that runs when a new session is started
 
    }
 
    void Session_End(object sender, EventArgs e) 
    {
        // Code that runs when a session ends. 
        // Note: The Session_End event is raised only when the 
sessionstate mode
        // is set to InProc in the Web.config file. If session 
mode is set to StateServer 
        // or SQLServer, the event is not raised.
 
    }
 
</script>

В приложении Starter Kit, например в Application_Start, создаются роли администратора и друга.

ViewState

В лекции 3 упоминалось о скрытом поле ViewState, которое обычно есть у сгенерированных ASP .NET страниц. Говорилось, что там хранится информация о состоянии отображения страницы, которая передается на сервер и обратно, чтобы после перезагрузки на ней сохранялись ее динамические изменения. Там не хранятся введенные пользователем данные, они отправляются, как обычно, в теле запроса POST.

Понимание, что такое состояние отображения и как оно работает, очень важно, особенно для разработчиков, которые пишут собственные элементы управления.

В HTML было введено скрытое поле <input type=hidden> именно для того, чтобы передавать нужную для программиста, но не нужную или не интересную для пользователя информацию на сервер. ASP .NET пользуется этими полями, при этом шифруя информацию (хотя опытный пользователь сможет ее расшифровать). Состояние отображения хранится в скрытом поле формы с идентификатором __VIEWSTATE.

Каким же образом генерируется это поле, что в нем хранится и когда оно читается при возврате формы? Чтобы это понять, надо более подробно рассмотреть жизненный цикл страницы. Каждый раз, когда на сервере генерируется страница, в первый раз или после постбэка, заново создается объект Page и все его элементы. Если свойство элемента управления декларировано на странице или меняется во время события инициализации, оно будет таким всегда.

http://www.intuit.ru/department/se/aspdotnet/15/15_02.png


Рис. 15.2. 

Из картинки видно, что между событиями Init и Load при постбэке происходят еще два события: загрузка состояния отображения и загрузка данных постбэка. И те, и другие читаются из тела запроса POST. Перед событием Render состояние отображения вновь записывается с помощью функции SaveViewState. Функция SaveViewState объекта рекурсивно вызывает ее же вложенных в нее элементов. При этом эта информация кодируется по алгоритму base-64. Во время отрисовки страницы создается скрытый элемент __VIEWSTATE. Следовательно, во время события Load или в обработчиках событий элементов управления можно изменять свойства страницы или любых ее элементов.

Свойство ViewState есть как у страницы, так и у всех элементов управления на ней.

Пусть у нас есть простая форма:

    <form id="form1" runat="server">
      <asp:Label ID="Label1" runat="server" ></asp:Label><br />
      <asp:Button ID="Button1" runat="server" 
OnClick="Button1_Click" Text="Get Time" /><br />
      <asp:Button ID="Button2" runat="server" Text="Button" />
    </form>

Во время первой загрузки страницы ничего связанного с сохранением состояния не происходит.

При нажатии на первую кнопку динамически меняется текст метки. Кнопка вызывает отправку данных на сервер, и во время второго жизненного цикла выполняется функция RaisePostBackEvent, которая вызывает обработчик:

  protected void Button1_Click(object sender, EventArgs e)
  {
    Label1.Text = DateTime.Now.ToLongTimeString();
  }

Измененное состояние метки сохраняется в свойстве ViewState. Если теперь нажать на вторую кнопку, то сразу после инициализации страницы в текст метки будет загружаться текст, сохраненный в ViewState. Если отключить возможность сохранения состояния, то текст метки вернется к своему пустому состоянию. То же самое произойдет и при нажатии на первую кнопку, но так как RaisePostBackEvent происходит после LoadPostBackData, значение, загруженное из ViewState, переписывается новым значением.

Состояние страницы доступно программисту, и в него можно добавлять дополнительные данные. Например, когда нужно было сортировать DataGrid, в переменную ViewState["sort"] записывалось выражение сортировки.

ViewState представляет собой коллекцию пар "ключ/значение", подобно объектам приложения и сессии. В него можно помещать только объекты с атрибутом Serializable. Свойство ViewState имеет тип System.Web.UI.StateBag, и синтаксис доступа к данным похож на доступ к значениям Hashtable.

Поле __VIEWSTATE может стать довольно большим (порядка десятков килобайт), например, когда в форме присутствует DataGrid или GridView. Эти элементы управления хранят в состоянии отображения все свое содержимое. И вся эта информация путешествует с клиента на сервер и обратно. Увеличивает это поле и элемент Wizard, и TreeView. В общем, чем больше возможностей и свойств, тем дороже приходится за это платить. Поэтому рассмотрим методы сокращения размера поля __VIEWSTATE. У каждого элемента управления есть свойство EnableViewState, которое по умолчанию равно True. Если установить его в False, то состояние данного элемента управления не будет записано в объект ViewState. Это свойство можно отключить и у всей страницы — с помощью атрибута EnableViewState директивы Page или программно:

Page.EnableViewState = false;

Такой же атрибут есть у директив MasterPage, Control.

Атрибут директивы Page EnableViewStateMac определяет, проводится ли машинный контроль аутентификации (MAC). Это контроль, гарантирующий, что состояние отображения не сфальсифицировано злонамеренными пользователями.

Хотя отключение EnableViewState позволяет уменьшить количество передаваемой информации, не всегда это можно делать безнаказанно. Если на странице есть DataGrid, который заполняется во время события Page_Load при условии if (!Page.IsPostBack), то есть при первой загрузке страницы, то после постбэка он просто исчезнет со страницы. Следовательно, при выключенном EnableViewState каждый раз необходимо читать данные из источника заново. Что выбрать, зависит от ситуации. В одних случаях бывает важнее сэкономить на времени загрузки страницы, в других — на соединении с базой данных.

Заключение

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

Web-службы

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

Предприятия на своих страницах предоставляют разнообразную информацию. Например, со своего сайта www.Ford.com компания "Форд" публикует информацию о моделях и ценах. Дилер этой компании хотел бы иметь эту информацию и на своем сайте. Web-служба позволяет сайту-потребителю получать информацию с сайта-поставщика. Сайт-потребитель показывает эту информацию на своих страницах. Код для генерации этой информации написан один раз, но может использоваться многими потребителями. Данные представлены в простом текстовом виде, поэтому ими можно пользоваться независимо от платформы.

Web-сервисы широко используются и в Desktop, и в Интернет-приложениях. Они сами по себе являются не приложениями, а источниками данных для приложений. У них отсутствует пользовательский интерфейс. Web-сервисами необязательно пользоваться через сеть — они могут быть частью проекта, в котором используются.

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

Это дальнейшее развитие компонентного программирования CORBA и DCOM. Однако для использования таких компонентов необходимо регистрировать их в системе потребителя. Для web-служб это не требуется. Компоненты хорошо работают в локальных сетях. Протокол HTTP не приспособлен для вызова удаленных процедур (RPC). Даже в одной организации часто используются разные операционные системы, которые могут взаимодействовать только через HTTP.

Было предпринято несколько попыток создания языка коммуникации между разнородными системами — DCOM, CORBA, RMI, IIOP. Они не получили всеобщего распространения, так как каждый из них продвигался разными производителями и поэтому был привязан к технологиям конкретного изготовителя. Никто не хотел принимать чужой стандарт. Чтобы выйти из этой дилеммы, несколько компаний договорились выработать независимый от производителя стандарт обмена сообщениями через HTTP. В мае 2000 года компании IBM, Microsoft, HP, Lotus, SAP, UserLand и другие обратились к W3C и выдвинули SOAP в качестве кандидата на такой стандарт. SOAP произвел революцию в области разработки приложений, объединив два стандарта Интернета — HTTP и XML.

SOAP

Для взаимодействия с web-сервисами применяется протокол SOAP, основанный на XML. Можно было бы использовать просто XML, но это слишком свободный формат, в нем каждый документ фактически создает свой язык. SOAP — это соглашение о формате XML-документа, о наличии в нем определенных элементов и пространств имен.

SOAP позволяет публиковать и потреблять сложные структуры данных, например DataSet. В то же самое время его легко изучить. Текущая версия SOAP — 1.2.

SOAP — это Простой Протокол Обмена Данными (Simple Object Access Protocol). SOAP создан для того, чтобы облегчать взаимодействие приложениям через HTTP. Это особый независимый от платформы формат обмена сообщениями через Интернет. Сообщение SOAP — это обычный XML-документ. Стандарт SOAP разрабатывает консорциум W3C.

SOAP-сообщение состоит из конверта (envelope), заголовка (header) и тела (body). Элементы body и envelope должны присутствовать всегда, а header необязателен. Элемент envelope — корневой. Элемент header может содержать специфичную для данного приложения информацию. В документе, сгенерированном web-сервисом, может присутствовать элемент ault, который описывает ошибку работы.

POST /WebSite5/WebService.asmx HTTP/1.1
Host: localhost
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
 
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <HelloWorld xmlns="http://localhost/webservices" />
  </soap12:Body>
</soap12:Envelope>

В ASP .NET можно легко как создать web-службу, так и пользоваться готовой.

Пользование web-службой

В Интернете существует множество готовых web-служб. Сайты http://uddi.microsoft.com, http://www.webservicelist.com/ — каталоги различных сервисов. Чтобы получить информацию от web-службы, нужно только послать HTTP-запрос, в теле которого находится SOAP-сообщение. Запрос к службе http://www.webservicex.net/globalweather.asmx на получение прогноза погоды в Москве выглядит так:

POST /globalweather.asmx HTTP/1.1

Host: www.webservicex.net

Content-Type: text/xml; charset=utf-8

Content-Length: length

SOAPAction: "http://www.webserviceX.NET/GetWeather"

 

<?xml version="1.0" encoding="utf-8"?>

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-

instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

  <soap:Body>

    <GetWeather xmlns="http://www.webserviceX.NET">

      <CityName>Moscow</CityName>

      <CountryName>Russian</CountryName>

    </GetWeather>

  </soap:Body>

</soap:Envelope>

Заголовок запроса отличается от запросов, которые обычно посылают браузеры, прежде всего полем Content-Type — text/xml; а не text/html; В теле запроса находится SOAP-сообщение.

Сервис в ответ оправляет XML-документ:

<?xml version="1.0" encoding="utf-8" ?>

  <string xmlns="http://www.webserviceX.NET">

<?xml version="1.0" encoding="utf-16"?>

 <CurrentWeather>

 <Location>Moscow / Vnukovo , Russia (UUWW) 55-39N 037-

16E</Location> <Time>Aug 07, 2006 - 04:30 AM EDT / 2006.08.07

0830 UTC</Time>

 <Wind> from the E (080 degrees) at 11 MPH (10 KT):0</Wind>

 <Visibility> greater than 7 mile(s):0</Visibility>

 <SkyConditions> overcast</SkyConditions>

 <Temperature> 66 F (19 C)</Temperature>

 <DewPoint> 55 F (13 C)</DewPoint>

 <RelativeHumidity> 68%</RelativeHumidity>

 <Pressure> 29.85 in. Hg (1011 hPa)</Pressure>

 <Status>Success</Status> </CurrentWeather></string>

Чтобы сделать проект ASP .NET потребителем web-сервиса, первым делом в проекте надо создать web-ссылку на удаленный объект — web-сервис. Выберите в меню Website пункт Add Web Reference. Появится диалоговое окно.

http://www.intuit.ru/department/se/aspdotnet/16/16_01sm.png


увеличить изображение
Рис. 16.1. 

Введите URL web-сервиса с параметром wsdl в текстовое поле www.webservicex.net/globalweather.asmx?WSDL. В файл web.config добавляется настройка приложения:

<configuration

xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

   <appSettings>

   <add key="weather.webservicex.www.globalweather"

value="http://www.webservicex.net/globalweather.asmx"/>

   </appSettings>

   <connectionStrings>

Чтобы определить доступные по этому адресу web-сервисы, используется алгоритм DISCO.

При этом создается файл globalweather.wsdl. WSDL (Web Service Discovery Language) — это язык описания web-сервисов. Это еще один тип XML-документов.

<?xml version="1.0" encoding="utf-8"?>

<wsdl:definitions

xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"

xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"

xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"

xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"

xmlns:tns="http://www.webserviceX.NET"

xmlns:s="http://www.w3.org/2001/XMLSchema"

xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"

targetNamespace="http://www.webserviceX.NET"

xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

  <wsdl:types>

    <s:schema elementFormDefault="qualified"

targetNamespace="http://www.webserviceX.NET">

      <s:element name="GetWeather">

Создание web-ссылки добавляет в конфигурационный файл еще одну запись:

<configuration

xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

   <appSettings>

      <add key="weather.webservicex.www.globalweather"

value="http://www.webservicex.net/globalweather.asmx"/>

   </appSettings>

Чтобы облегчить работу с web-сервисами, используют прокси-классы. Они предоставляют разработчикам удобные функции и берут на себя преобразование их параметров в элементы XML, после чего посылают запрос web-сервису через Интернет.

Утилита wsdl поможет преобразовать его в прокси-класс:

wsdl globalweather.wsdl

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

Программу wsdl можно запустить и удаленно:

wsdl http://www.webservicex.net/globalweather.asmx?wsdl

В созданном файле объявлен класс GlobalWeather, наследник System.Web.Services.Protocols.SoapHttpClientProtocol. Функции этого класса предназначены как для синхронного, так и для асинхронного вызова. Например, синхронная функция GetWeather запрашивает строковые параметры с названием города и страны и возвращает строку с XML-документом. В сервисе есть и другая функция, с помощью которой можно узнать доступные города для каждой страны:

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http:/

/www.webserviceX.NET/GetWeather",

RequestNamespace="http://www.webserviceX.NET",

ResponseNamespace="http://www.webserviceX.NET",

Use=System.Web.Services.Description.SoapBindingUse.Literal,

ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wr

apped)]

    public string GetWeather(string CityName, string CountryName)

{

        object[] results = this.Invoke("GetWeather", new object[]

{

                    CityName,

                    CountryName});

        return ((string)(results[0]));

    }

Это текст можно использовать на странице:

    <form id="form1" runat="server">

      <br />

      <asp:Button ID="Button1" runat="server" Text="А теперь про-

гноз погоды" OnClick="Button1_Click" />

      <br />

      <br />

      <asp:Label ID="lblTemp" runat="server" Text="Температура в

Москве "></asp:Label>

    </form>

 

  protected void Button1_Click(object sender, EventArgs e)

  {

// создание экземпляра прокси-класса

    GlobalWeather gw = new GlobalWeather();

// запрос функции web-сервиса

    string xmlstring = gw.GetWeather("Moscow", "Russia");

    XmlDocument doc = new XmlDocument();

// загрузка ответа в документ

    doc.LoadXml(xmlstring);

// doc.ChildNodes.Item(0) — это XML-заголовок

// doc.ChildNodes.Item(1) — тело

    XmlNode child = doc.ChildNodes.Item(1);

    XmlElement el = child["Temperature"];

    lblTemp.Text += el.InnerText;

  }

Создание web-сервиса

Создание web-сервиса немногим отличается от создания обычной страницы. Есть два варианта: можно создать отдельный проект или вставить web-сервис в существующий проект. В первом случае другие проекты должны создавать web-ссылку, чтобы обращаться к сервисам этого проекта. Файл web-сервиса имеет расширение asmx. Файл web-сервиса должен начинаться с директивы WebService. Класс web-сервиса может быть потомком класса System.Web.Services.WebService.

Если при объявлении web-сервиса вы породили его от класса System.Web.Services.WebService, то вы автоматически получаете доступ к глобальным объектам приложения ASP .NET Application, Context, Session, Server и User. Если же вы создавали класс web-сервиса как-то иначе — ничего страшного. Вы все равно можете получить доступ к вышеперечисленным свойствам с помощью соответствующих свойств статического HttpContext.Current.

Методы, которые сервис предоставляет для вызова с помощью SOAP-запросов, должны иметь атрибут WebMethod. У атрибута WebMethod существует 6 свойств, влияющих на работу метода.

Создадим новое приложение в VS .NET и добавим к нему файл web-сервиса Customers.asmx.

Файл nw.asmx содержит единственную строку — директиву WebService, которая утверждает, что этот файл — web-сервис. Этот файл меняться не будет:

<%@ WebService Language="C#" CodeBehind="~/App_Code/Customers.cs"

Class=" Customers " %>

У директивы WebService всего 4 возможных атрибута. CodeBehind, который раньше был атрибутом и директивы Page, определяет физический путь к файлу отделенного кода. Атрибут Class обязателен и определяет класс, который реализует функциональность web-сервиса. Debug и Language аналогичны тем же атрибутам директивы Page:

Файл с расширением .asmx — точка входа создаваемого web-сервиса.

Класс System.Web.Services.WebService, которые обычно наследуется класс сервиса, предоставляет доступ к глобальным объектам Application и ViewState.

Весь код web-сервиса будет располагаться в codebehind-файле Service.asmx.cs. Изначально этот файл (созданный в Visual Studio .NET) имеет следующий вид:

<%Class="WebService" %>

 

using System;

using System.Web;

using System.Collections;

using System.Web.Services;

using System.Web.Services.Protocols;

 

 

/// <summary>

/// Summary description for WebService

/// </summary>

[WebService(Namespace = "http://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

public class MyWebService : System.Web.Services.WebService {

 

    public WebService () {

 

        //Uncomment the following line if using designed compo-

nents

        //InitializeComponent();

    }

 

    [WebMethod]

    public string HelloWorld() {

        return "Hello World";

    }

   

}

Атрибут WebServiceBinding удостоверяет соответствие откликов web-сервиса WS-I Basic Profile 1.0 release требованиям организации WS-I (Web Services Interoperability organization), которая занимается вопросами межплатформенной совместимости web-сервисов.

Метод HelloWorld создан Visual Studio в качестве примера начинающим разработчикам.

Web-сервис может состоять из множества классов. Однако только один класс в web-сервисе может иметь методы, помеченные атрибутом WebMethod (которые можно вызывать через SOAP-запросы).

Когда страница web-сервиса запрашивается в браузере, он возвращает автоматически генерируемую страницу помощи по данному сервису. Откроем в браузере страницу WebService.asmx. В браузере появится страница:

http://www.intuit.ru/department/se/aspdotnet/16/16_02.png


Рис. 16.2. 

ASP .NET для отображения web-сервиса использует файл шаблона DefaultWsdlHelpGenerator.aspx, расположенный в папке <%SYSTEM_ROOT%\Microsoft.NET\Framework\<version>\CONFIG. На выводимой странице web-сервиса есть название web-сервиса, ссылка на описание сервиса и список web-методов, объявленных в web-сервисе. Остальная часть страницы посвящена тому, что необходимо изменить пространство имен по умолчанию для web-сервиса http://tempuri.org/ на собственное. Поступим как рекомендуют — изменим параметр Namespace атрибута WebService класса web- сервиса:

[WebService(Namespace = "http://Lection-16.asp2.intuit.org/")]

Кроме того, в атрибуте WebService можно задать свойства Name и Description, и они появятся на странице помощи по web-сервису.

Итак, перейдем к странице описания web-метода SayHello (просто кликните по ссылке SayHello на странице описания web-сервиса).

Как видите, на ней также присутствует название web-сервиса, ссылка на первую страницу web-сервиса, название web-метода. Кроме этого, на странице расположена кнопка Invoke, предназначенная для вызова web-метода через GET-запрос (данное правило не выполняется, если web-метод не может быть вызван таким образом), и примеры запросов для вызова данного web-метода с помощью SOAP, HTTP POST (если такой вызов возможен) и HTTP GET (если такой вызов возможен). Также представлены примеры ответов вызова web-метода.

Протестируем нашу работу с помощью страницы web-сервиса:

<?xml version="1.0" encoding="utf-8" ?> 
  <string xmlns="http://localhost/webservices">Hello World</string>

От такого web-сервиса нет особенной пользы, поэтому создадим новый сервис nw и вместо метода HelloWorld напишем метод, который обращается к базе данных Northwind и возвращает DataSet:

[WebService(Name="Northwind web service", Description = "Web-сер-
вис для работы с клиентами",
  Namespace = "http://intuit.asp2.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class nw : System.Web.Services.WebService
{
 
  public nw()
  {
  }
  string strConn = @"Data Source=.\SQLExpress;Initial 
Catalog=Northwind;Integrated Security=True";
  [WebMethod(MessageName = "Get Orders of Customers", Description 
= "Web-метод для работы с заказами", CacheDuration = 600)]
  public DataSet GetCustOrders(string CustomerID)
  {
    SqlConnection myConn = new SqlConnection(strConn);
    SqlDataAdapter myData = new 
SqlDataAdapter("CustOrdersOrdersDetails", myConn);
    myData.SelectCommand.CommandType = 
CommandType.StoredProcedure;
    myData.SelectCommand.Parameters.Add(new 
SqlParameter("@CustomerID", SqlDbType.Char, 5));
    myData.SelectCommand.Parameters["@CustomerID"].Value = 
CustomerID;
 
    DataSet ds = new DataSet();
    myData.Fill(ds);
    ds.Tables[0].TableName = "Orders";
    ds.Tables[1].TableName = "OrderDetails";
    ds.Relations.Add(ds.Tables[0].Columns["OrderID"], 
ds.Tables[1].Columns["OrderID"]);
    return ds;
  }

Другие аргументы атрибуты WebMethod:

1. CacheDuration определяет промежуток времени в секундах, на которое кэшируется web-сервис. По умолчанию равно 0, что значит, что кэширование отключено.

В этом объявлении значение, возвращаемое методом GetCustOrders, кэшируется на 10 минут.

2. Description — описание метода, которое выводится на странице сервиса под ссылкой на страницу метода.

3. EnableSession позволяет включить поддержку сессий. По умолчанию поддержка сессий в web-сервисах отключена. Чтобы включить ее, определите web-метод следующим образом:

[WebMethod(EnableSession=true)]

4. MessageName определяет уникальное имя метода. Позволяет использовать перегруженные функции с одним именем, но разными сигнатурами.

5. TransactionOption управляет поддержкой транзакций. По умолчанию она отключена. Web-сервисы ограниченно поддерживают транзакции, то есть веб-сервис может порождать транзакцию, но при этом не может быть участником другой транзакции. Возможные значения аргумента — Disabled, NotSupported, Supported, Required, и RequiresNew. Если вызывается web-метод с TransactionOption, установленным в Required или RequiresNew, а в нем вызывается другой web-метод с такими же установками, каждый из этих методов инициирует свою транзакцию.

Метод GetCustOrders возвращает тип DataSet. Посмотрим, как он помещается в SOAP:

<?xml version="1.0" encoding="utf-8" ?> 
- <DataSet xmlns="http://intuit.asp2.org/">
- <xs:schema id="NewDataSet" xmlns="" 
xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xs:element name="NewDataSet" msdata:IsDataSet="true" 
msdata:UseCurrentLocale="true">
- <xs:complexType>
- <xs:choice minOccurs="0" maxOccurs="unbounded">
- <xs:element name="Orders">
- <xs:complexType>
- <xs:sequence>
  <xs:element name="OrderID" type="xs:int" minOccurs="0" /> 
  <xs:element name="OrderDate" type="xs:dateTime" minOccurs="0" /> 
  <xs:element name="RequiredDate" type="xs:dateTime" minOccurs="0" /> 
  <xs:element name="ShippedDate" type="xs:dateTime" minOccurs="0" /> 
  </xs:sequence>
  </xs:complexType>
  </xs:element>
...
  <xs:selector xpath=".//OrderDetails" /> 
  <xs:field xpath="OrderID" /> 
  </xs:keyref>
  </xs:element>
  </xs:schema>
  <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msda-
ta" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1" /> 
  </DataSet>

Это почти готовая схема xsd. Из нее можно, например, сгенерировать класс бизнес-логики.

Использовать наш сервис можно и удаленно, и из приложения, в котором он находится.

Создадим форму, которая будет отображать полученный DataSet в элементе управления GridView:

<form id="form1" runat="server">
    <div>
      <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString 
%>"
        SelectCommand="SELECT CustomerID, CompanyName FROM 
Customers Order by CompanyName"
        ProviderName="<%$ 
ConnectionStrings:NorthwindConnectionString.ProviderName 
%>"></asp:SqlDataSource>
      <asp:DropDownList ID="DropDownList1" runat="server" 
DataSourceID="SqlDataSource1"
        DataTextField="CompanyName" DataValueField="CustomerID">
      </asp:DropDownList></div>
    <asp:Button ID="Button1" runat="server" Text="Get Orders" />
    <asp:GridView ID="GridView1" runat="server">
    </asp:GridView>
  </form>

Имя компании выбирается из выпадающего списка. После нажатия на кнопку вызывается обработчик:

  protected void Button1_Click(object sender, EventArgs e)
  {
    localhost.Northwindwebservice orders = new 
localhost.Northwindwebservice();
    GridView1.DataSource = 
orders.GetCustOrders(DropDownList1.SelectedValue);
    GridView1.DataBind();
  }

Стать потребителем этого сервиса могло бы и обычное Windows-приложение, написанное на C++, C# или Visual Basic. Web-сервисы тоже могут пользоваться услугами других web-сервисов.

Заключение

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

Ресурсы проекта

"Для каждого предусмотрен его личный конец света."

Хенрик Ягодзиньский

Создание многоязычных web-сайтов имеет особенно большое значение в неанглоговорящих странах. Изначально ASP .NET настроена на английский язык, причем на его американскую разновидность. Причины этого очевидны. Но платформа .NET поддерживает концепцию информации о культуре, а строки хранятся в формате Unicode, что позволяет писать их на множестве языков. Глобализация — это создание приложений, способных работать в разных культурных средах. Локализация — создание ресурсов для работы с конкретной культурой. Ресурсы должны быть отделены от программного кода.

Классы для работы с информацией о культурах заключены в пространстве имен Globalization. Класс CultureInfo содержит свойство CurrentCulture, которое позволяет узнать все данные о текущей культуре — форматы отображения, календарь, кодовую страницу и другие.

Файлы ресурса содержат строки, которые могут быть написаны на разных языках для различных культурных сред. Формат этих файлов — XML, следующий специальной схеме Microsoft ResX. Файлы .resx автоматически включаются в сборку для использования на страницах. Кроме строк, файлы ресурса могут содержать картинки и другие файлы. Их можно использовать для создания многоязычных приложений. В отличие от предыдущих версий, ресурсы не нужно компилировать вручную в сборку-сателлит — ASP .NET 2.0 делает это сама.

В папке App_GlobalResources хранятся файлы ресурсов, названия которых соответствуют культурной схеме. Например, ресурс для русской культуры называется Resource.ru-ru.resx, для финской — Resource.fi-FI.resx. Ресурс с нейтральной культурой называется просто Resource.resx. Ресурсы доступны для всех страниц и пользовательских элементов управления. Промежуточное расширение следует стандарту .NET на региональные стандарты — состоит из главного и вспомогательного тегов.

В папке \App_LocalResources хранятся локальные файлы ресурсов для конкретных страниц. Название файла ресурса формируется из имени страницы, кода культурной среды и расширения resx. Например, default.aspx.de.resx — это файл ресурса на немецком языке для страницы default.aspx.

Протокол HTTP позволяет браузерам посылать список предпочитаемых языков на сервер. В браузере можно настроить предпочтительные языки web-страниц. ASP .NET позволяет автоматически модифицировать культуру страницы в зависимости от первого языка в списке. Для этого атрибуту Culture директивы Page нужно присвоить значение auto. Так же обстоит дело у атрибута UICulture — он определяет культуру пользовательского интерфейса. Менеджер ресурсов ищет строки и другие ресурсы в файле с тем расширением, которое определено в атрибуте UICulture. Формат отображения дат, чисел и денежной информации определяется атрибутом Culture.

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

Свойства Culture и UICulture — страницы строковые и принимают значения в длинном формате, например, "English (United States)":

<%@ Page Language="C#"Culture="Auto" UICulture="Auto" %>

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

Загрузить строку из файла ресурсов можно по-разному. Первый способ — использовать класс Resource. Предварительно в App_Global Resources нужно создать файлы Resource.resx и Resource.ru-RU.resx со строками.

Resource.resx

Name

Value

Answer

Good morning,

PageTitle

Sample Globalization Page

Question

What is your name?

Resource.ru-RU.resx

Name

Value

Answer

Привет

PageTitle

Пример глобализации ASP .NET

Question

Как Вас зовут?

<%@ Page Language="C#"Culture="Auto" UICulture="RU-ru" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<script runat="server">

    protected void Page_Load(object sender, System.EventArgs e)

    {

        Page.Title = Resources.Resource.PageTitle;

    }

   

    protected void Button1_Click(object sender, System.EventArgs

e)

    {

      Localize1.Text = Resources.Resource.Answer + ", " +

Textbox1.Text;

 

    }

</script>

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head id="Head1" runat="server">

    <title></title>

</head>

<body>

    <form id="Form1" runat="server">

        <p><%= Resources.Resource.Question %></p><br />

        <asp:TextBox ID="Textbox1"

Runat="server"></asp:TextBox><br />

        <asp:Button ID="Button1" Runat="server" Text="Submit"

         OnClick="Button1_Click" />

        <p>

          <asp:Localize ID="Localize1"

runat="server"></asp:Localize>

 

        </p>

    </form>

</body>

</html>

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

<%@ Page Language="C#" AutoEventWireup="true"  Culture="auto:en-

US" meta:resourcekey="PageResource1" UICulture="auto:en-US" %>

 

<asp:Button ID="ConvertButton" runat="server" Text="Convert"

OnClick="ConvertButton_Click" meta:resourcekey="ButtonResource1" />

В LocalizeImp_cs.aspx.resx содержится строка ButtonResource1.Text со значением Convert, в LocalizeImp_cs.aspx.de.resx со значением Konvertieren, в файлах ресурсов для других языков — перевод слова на эти языки.

Посмотрите пример полностью в QuickStart: C:\Program Files\ Microsoft Visual Studio 8\SDK\v2.0\QuickStart\aspnet\samples\localization\LocalizeImp_cs.

Аутентификация и авторизация

Важная часть многих web-приложений — возможность контролировать доступ к ресурсам. Безопасность проекта вращается вокруг двух концепций — аутентификации и авторизации. Аутентификация — процесс определения личности пользователя. Авторизация — процесс определения прав пользователя на доступ к определенным ресурсам, на просмотр некоторых разделов, на возможность редактирования информации и так далее.

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

В ASP .NET 2.0 аутентификацией управляют с помощью службы Membership, которая позволяет определить различные виды членства на сайте. Информацию о членах можно хранить в различных местах — в базах данных, текстовых файлах или даже в учетных записях Windows. Конфигурировать членство можно индивидуально для каждого пользователя или на основе ролей с помощью сервиса Role Manager. Роли облегчают конфигурирование, так как можно создавать роли и потом добавлять пользователей к готовым ролям. Любому пользователю может принадлежать любое количество ролей.

По умолчанию службы используют провайдера AspNetSqlProvider. В таком случае ASP .NET автоматически создает базу данных ASPNETDB.MDF в директории проекта App_Data, когда запускается команда ASP.NET Configuration — или программно, или с помощью элементов управления группы Login задействуются службы Membership или Role Manager. Служба Membership также обеспечена провайдером, работающим с Active Directory. Role Manager может работать с провайдером, связанным с Authorization Manager операционной системы Widnows 2003. API, связанное со службой, позволяет настроить ее и на использование пользовательского провайдера.

Роли имеют определенные права. Например, администратор сайта может изменять настройки, редактировать членство других пользователей. Обычный пользователь с ролью Friend может просмотреть частные альбомы администратора, а неавторизованный пользователь — только публичные. Эта модель реализована в приложении Starter Kit. В этом приложении роли создаются во время первого запуска:

void Application_Start(object sender, EventArgs e) {

   SiteMap.SiteMapResolve += new

SiteMapResolveEventHandler(AppendQueryString);

   if (!Roles.RoleExists("Administrators"))

Roles.CreateRole("Administrators");

   if (!Roles.RoleExists("Friends"))

Roles.CreateRole("Friends");

}

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

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

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

В этом приложении можно также создавать правила доступа.

Настройки конфигурации служб Membership и Role Manager, конечно же, записываются в файл Web.config:

   <roleManager enabled="true" />

Таким образом включается служба Role Manager.

Элемент authentication mode определяет способ аутентификации. Если это Forms, то свои имя и пароль пользователь вводит в форме. В локальной сети (интранет) можно аутентифицировать пользователей по их учетной записи, тогда его значение ставится как Windows.

С помощью элемента authentication запускается служба Membership:

    <authentication mode="Forms">

      <forms loginUrl ="Login.aspx"/>

    </authentication>

Другие доступные значения — Passport и None.

При значении Passport пользователи авторизуются с помощью паспорта от Microsoft.

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

Всего у элемента forms 8 возможных атрибутов:

По умолчанию всем пользователям доступны все страницы приложения. Если нужно ограничить доступ пользователей к какой-нибудь странице, в Web.config вводится запись:

  <location path="Admin.aspx">
    <system.web>
      <authorization>
        <allow roles="Admin" />
        <deny users="*" />
      </authorization>
    </system.web>
  </location>

Значение "?" обозначает анонимного пользователя, а "*" — всех пользователей:

        <allow users="?" />

В элементах allow и deny пользователи и роли могут быть заданы перечислением:

        <allow users="Alex, Dave" />

Элемент location определяет часть сайта, доступ к которой нужно ограничить. В данном случае это одна страница Admin.aspx. Первая часть авторизации разрешает доступ к ней пользователям в роли администратора Admin. Вторая запрещает доступ всем остальным пользователям.

Если элемент system.web находится в корневом узле файла, то вложенный в него узел authorization определяет настройки доступа ко всему сайту.

Когда неавторизованный пользователь пытается получить доступ к странице, открывается форма, определенная в атрибуте forms loginUrl. Если он введет имя и пароль пользователя, который имеет доступ к странице, то она откроется, иначе опять ему или ей будет показана форма логина.

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

    <siteMap defaultProvider="AspXmlSiteMapProvider" 
enabled="true">
      <providers>
        <clear/>
        <add name="AspXmlSiteMapProvider" 
type="System.Web.XmlSiteMapProvider, System.Web, 
Version=2.0.3600.0, Culture=neutral, 
PublicKeyToken=b03f5f7f11d50a3a"
          siteMapFile="web.sitemap" 
securityTrimmingEnabled="true"/>
      </providers>
    </siteMap>

Здесь атрибут securityTrimmingEnabled, установленный в true, заставляет провайдера карты сайта фильтровать узлы в зависимости от роли пользователя. Это значит, что в элементах навигации будут видны только доступные страницы.

Член User страницы позволяет получить доступ ко всей информации, касающейся текущего пользователя.

Метод IsInRole определяет, принадлежит ли пользователь к роли:

  if (User.IsInRole("Admin"))
      Page.Title = "Hello, Administrator!";

Свойство Identity дает информацию об аутентификации пользователя:

if (User.Identity.IsAuthenticated)
//выполнить код, если пользователь легален

User.Identity.AuthenticationType показывает способ авторизации, установленный в Web.config.

Можно ограничить доступ не только к страницам, но и к частям страниц.

 

name

Определяет имя файла- cookie, который посылается пользователям после аутентификации. По умолчанию он называется .ASPXAUTH

loginUrl:

URL страницы, с которой можно войти в систему. По умолчанию это Login.aspx

protection:

Уровень защиты файла- cookie на пользовательской машине. Возможные значения — All, None, Encryption, и Validation

timeout:

Время, по истечении которого cookie устаревает. Значение по умолчанию — 30 минут

path:

Путь к файлам cookie

requireSSL:

Нужно ли шифровать данные по протоколу SSL

slidingExpiration:

Если True, то cookie устаревает через период времени timeout после последнего запроса. Иначе — через период времени timeout после создания

cookieless:

Место хранения идентификатора пользователя. Значение по умолчанию useDeviceProfile

Элементы управления группы Login

Мы уже создавали формы регистрации. Это было только упражнение, так как элементы управления этой группы могут брать на себя регистрацию и авторизацию пользователей, восстановления пароля и другие функции, взаимодействуя при этом с системой Membership и Roles. Группа Login находится в панели инструментов.

Элемент управления LoginName позволяет показать имя пользователя:

Заходите еще, <asp:LoginName ID="FCLoginName" Runat="server" />

Отображаемый текст можно форматировать с помощью FormatString:

<asp:LoginName ID="HelloLoginName" Runat="server" FormatString="Hello, {0}" />.</p>

LoginStatus — это умный элемент управления. Если пользователь не аутентифицирован, отображается гиперссылка Login на страницу входа на сайт. Если пользователь авторизован, это ImageButton с командой Logout. Если ее нажать, то пользователь выходит из системы. Текст ссылок можно менять в свойствах LoginText и LogoutText или использовать изображения:

  <asp:LoginStatus ID="LoginStatus1" runat="server" 
LoginText="Вход" LogoutText="Выход" />
  <asp:LoginStatus ID="LoginStatus2" runat="server" 
LogoutAction="Redirect" LogoutPageUrl="~/Default.aspx" 
LoginImageUrl="~/Images/arrow_next.gif" 
LogoutImageUrl="Images/arrow_prev.gif" />

Нажатие на ссылку Logout в этом случае перенаправит пользователя на страницу Default.aspx.

Элемент управления LoginView состоит из двух шаблонов: AnonymousTemplate и LoggedInTemplate. Который из них используется для отображения, зависит от того, просматривает ли страницу анонимный пользователь или авторизованный. На странице default.aspx Starter Kit это единственный элемент на странице Registration.aspx:

<asp:loginview id="LoginArea" runat="server">
    <AnonymousTemplate>
        <asp:login id="Login1" runat="server">
            <layouttemplate>
                <div class="login">
                    <h4>Login to Site</h4>
  <asp:label runat="server" id="UserNameLabel" CssClass="label" 
associatedcontrolid="UserName">User Name</asp:label>
  <asp:textbox runat="server" id="UserName" cssclass="textbox" 
accesskey="u" />
  <asp:requiredfieldvalidator runat="server" id="UserNameRequired" 
controltovalidate="UserName" validationgroup="Login1" errormes-
sage="User Name is required." tooltip="User Name is required." 
>*</asp:requiredfieldvalidator>
  <asp:label runat="server" id="PasswordLabel" CssClass="label" 
associatedcontrolid="Password">Password</asp:label>
  <asp:textbox runat="server" id="Password" textmode="Password" 
cssclass="textbox" accesskey="p" />
  <asp:requiredfieldvalidator runat="server" id="PasswordRequired"
controltovalidate="Password" validationgroup="Login1" 
tooltip="Password is required." >*</asp:requiredfieldvalidator>
  <div><asp:checkbox runat="server" id="RememberMe" text="Remember 
me next time"/></div>
  <asp:imagebutton runat="server" id="LoginButton" 
CommandName="Login" AlternateText="login" skinid="login" 
CssClass="button"/> or
  <a href="register.aspx" class="button"><asp:image id="Image1" 
runat="server" AlternateText="create a new account" skinid="cre-
ate"/></a>
  <p><asp:literal runat="server" id="FailureText" enableview-
state="False"></asp:literal></p>
                </div>
            </layouttemplate>
        </asp:login>
    </anonymoustemplate>
    <LoggedInTemplate>
       <h4><asp:loginname id="LoginName1" runat="server" format-
string="Welcome {0}!" /></h4>
    </LoggedInTemplate>
    </asp:loginview>

Анонимному пользователю предоставляется возможность зайти или зарегистрироваться, а авторизованный видит приветствие со своим именем.

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

    <RoleGroups>
        </asp:RoleGroup>
        <asp:RoleGroup Roles="Moderator">
Вы можете удалять чужие сообщения.
            <ContentTemplate>
            </ContentTemplate>
        </asp:RoleGroup>
        <asp:RoleGroup Roles="ClubMember">
            <ContentTemplate>
Вы можете заходить в клубный форум.
            </ContentTemplate>
    </RoleGroups>

Шаблоны групп просматриваются по порядку, и показывается тот из них, что найден первым из групп, в которые входит пользователь. Например, если она принадлежит к группам Moderator и ClubMember, будет показан шаблон Moderator. Роли можно перечислять через запятую, тогда шаблон применим ко всем указанным группам:

<asp:RoleGroup Roles="Moderator, Administrator">

Остальные элементы управления этой группы — формы и мастера — Login, PasswordRecovery, ChangePassword.

CreateUserWizard позволяет создавать пользователей, используя службу Membership. Естественно, в нем происходит валидация введенных данных. Например, длина пароля должна быть не меньше 7 знаков и в нем должен присутствовать хотя бы один символ — не буква и не цифра. Обязательно заполнение контрольного вопроса и ответа, по которым можно будет восстановить пароль, если он забыт, или изменить пароль:

ChangeUser

Персонализация

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

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

Модель персонализации проста и расширяема.

В файле web.config содержится информация о том, какие данные о пользователе необходимо хранить. Она записывается в секции <configuration><system.web> перед секцией authentication:

    <profile>
      <properties>
        <add name="FirstName" />
        <add name="LastName" />
        <add name="LastVisited" />
        <add name="Age" />
        <add name="Member" />
      </properties>
    </profile>

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

    <anonymousIdentification enabled="true" />

(так же в секции <system.web>).

Когда анонимная персонализация включена, ASP .NET хранит уникальный идентификатор для каждого анонимного пользователя. Он посылается вместе с любым запросом. Идентификатор хранится в файле-cookie пользователя, а дополнительные данные, которые удалось собрать о его предпочтениях, — на сервере. По умолчанию имя файла-cookie — .ASPXANONYMOUS. Его можно поменять с помощью атрибута cookieName элемента anonymousIdentification:

    <anonymousIdentification enabled="true" cookieName=".intuit"/>

Время хранения файла-cookie в минутах определяется атрибутом cookieTimeout.

По умолчанию оно равно 100 000 минутам (69,4 дня).

От использования cookie можно отказаться, например, написав

cookieless="UseUri"

В таком случае идентификатор передается через строку URL:

cookieless="AutoDetect"

В этом случае определяются настройки пользователя. Если возможность хранить cookie выключена, используется строка URL.

Анонимный идентификатор по своей сути представляет собой GUID (32-байтное число, алгоритм генерации которого гарантирует глобальную уникальность). Свойство AnonymousId объекта Request страницы позволяет получить к нему доступ.

Для того чтобы определить, какие данные можно хранить для анонимного пользователя, в элемент add name определяют атрибут allowAnonymous:

        <add name="LastVisited" allowAnonymous="true"/>

Заключение

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

Элемент управления FileUpload

Формы HTML позволяют загружать пользовательские файлы на сервер. Для этого нужно установить атрибут enctype как "multipart/form-data" и в нем должен находиться элемент <input type="file">. Элемент управления FileUpload облегчает эту работу. Нужно вставить его в форму, а enctype установится автоматически. Элемент состоит из строки ввода и кнопки с надписью Browse. Процесс загрузки начинается после подачи формы на сервер, обычно для этого вставляют еще одну кнопку:

    <form id="form1" runat="server">
    <div>
        <asp:FileUpload ID="FileUpload1" runat="server" /><br>
        <asp:Button ID="UploadButton" runat="server"
            Text="Button" OnClick="UploadButton_Click" /><br><br>
            <asp:Label ID="Message" runat="server" Text="Label" 
BackColor="#FFC0C0" Width="354px"></asp:Label>
    </div>
    </form>

После того как файл загружен, FileUpload позволяет узнать его свойства. Файл находится в кэше сервера, пока не будет сохранен на диск методом SaveAs:

    protected void UploadButton_Click(object sender, EventArgs e)
    {
        if (this.FileUpload1.HasFile)
        {
            try
            {
                FileUpload1.SaveAs("c:\\Uploads\\" + 
FileUpload1.FileName);
                Message.Text = "Имя файла: " +
                FileUpload1.PostedFile.FileName + "<br>" +
                FileUpload1.PostedFile.ContentLength + " кб<br>" +
                "Content type: " +
                FileUpload1.PostedFile.ContentType;
            }
            catch (Exception ex) 
            {
                Message.Text = "Ошибка: " + ex.Message.ToString();
            }        
        }
    }

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

Свойство PostedFile имеет тип HttpPostedFile. Можно перенаправить содержимое загруженного файла в файловый поток с помощью свойства InputStream.

MultiView

Элемент управления MultiView позволяет создавать несколько представлений одной страницы и переключаться с одного на другой. MultiView состоит из элементов View, в которых находится часть страницы. В каждый момент времени видимым является один из элементов View. Это определяется свойством ActiveViewIndex.

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

    <asp:RadioButtonList ID="RadioButtonList1" runat="server" 
OnSelectedIndexChanged="RadioButtonList1_SelectedIndexChanged" 
AutoPostBack="True">
    <asp:ListItem Value="English"></asp:ListItem>
    <asp:ListItem Value="German"></asp:ListItem>
    <asp:ListItem Value="Russian"></asp:ListItem>
    </asp:RadioButtonList>
    <asp:MultiView ID="MultiView1" runat="server">
        <asp:View ID="View1" runat="server" >
        <b>We are glad to meet you on our yellow pages.</b>
        <br />
        <i>Information about over 8800 products and services.</i>
        </asp:View>
        <asp:View ID="View2" runat="server">
       <b>Wir sind erfreut Sie auf unseren Gelb-Seiten zu emp-
fangen!</b>
        <br />
        <i>Wir stellen zur Eur VerfЯgung Informationen von Яeber 
8800 Waren und Leistungen.</i>
        </asp:View>
        <asp:View ID="View3" runat="server">
        <b>Мы рады приветствовать вас на нашем сайте.</b>
        <br />
        <i>Предоставляем информацию о свыше чем 8800 товарах и 
услугах!</i>
        </asp:View>
    </asp:MultiView>

Переключение между представлениями происходит в обработчике списка переключателей RadioButtonList1:

    protected void RadioButtonList1_SelectedIndexChanged(object 
sender, EventArgs e)
    {
        MultiView1.ActiveViewIndex = 
RadioButtonList1.SelectedIndex;
    }

Wizard

Как и MultiView, элемент управления Wizard (Мастер, Волшебник) может создать последовательность шагов. Элемент управления Wizard позволяет вводить информацию в нескольких формах последовательно. Например, при регистрации основные сведения — имя и пароль — вводятся на первой форме. После того как эти данные успешно введены и прошли проверку на валидность, собираются дополнительные данные — адрес, телефон. На последней форме введенные данные можно показать в обобщенном виде для подтверждения.

Wizard описывается с помощью тега asp:Wizard, в который вложены элементы WizardStep.

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

Кнопки для перехода с шага на шаг — First, Next, Previous, Finish. На первой странице отображается только кнопка Next, на следующих — Previous и Next, и на последней — Previous и Finish. Если установить свойство DisplayCancelButton, к ним добавится кнопка отмены Cancel. Можно задать адрес страниц, куда будет перенаправлена форма при нажатии на кнопки Finish и Cancel. Как боковую панель, так и кнопочную панель навигации можно превратить в шаблоны. Показ боковой панели можно отключить свойством DisplaySideBar, установленным в False. Кнопки могут быть обычными или с любым изображением. Текст кнопок можно установить с помощью свойств CancelButtonText, FinishStepButtonText, FinishStepPreviousButtonText, NextStepButtonText, PreviousStepButtonText и the StartStepNextButtonText. Заголовок тоже можно превратить в шаблон.

Например, превратим простую форму регистрации (из лекции 5) пользователей в мастера. В отличие от предыдущего примера, в форме создавать кнопки не нужно, так как у Wizard кнопки есть. При этом, если в форме поставлены элементы-валидаторы, переход на следующий шаг кнопкой Next невозможен:

<asp:Wizard ID="Wizard1" runat="server" ActiveStepIndex="0"

Height="297px" OnFinishButtonClick="Wizard1_FinishButtonClick"

  Width="609px" OnActiveStepChanged="Wizard1_ActiveStepChanged"

DisplayCancelButton="True"

  HeaderText="Регистрация" BackColor="#F7F6F3" BorderColor="#CCCC-

CC" BorderStyle="Solid"

  BorderWidth="1px" Font-Names="Verdana" Font-Size="0.8em">

  <WizardSteps>

    <asp:WizardStep ID="Step1" runat="server" Title="Информация о

пользователе">

      <table>

        <tr>

          <td style="width: 100px">

            <asp:Label ID="Label1" runat="server" Text="Введите

имя:" Width="140px"></asp:Label>

          </td>

          <td style="width: 100px">

            <asp:TextBox ID="txtName" runat="server"

CausesValidation="True" Width="160px" />

          </td>

          <td style="width: 29px">

            <asp:RequiredFieldValidator

ID="RequiredFieldValidator1" runat="server"

ControlToValidate="txtName"

              ErrorMessage="Необходимо ввести

имя">*</asp:RequiredFieldValidator>

          </td>

        </tr>

        <tr>

          <td style="width: 100px">

            <asp:Label ID="Label2" runat="server" Text="Введите

адрес:" Width="140px"></asp:Label>

          </td>

          <td colspan="2">

            <asp:TextBox ID="txtAddress" runat="server"

TextMode="MultiLine" Rows="5" Width="187px" />

          </td>

        </tr>

        <tr>

          <td style="width: 100px">

            <asp:Label ID="Label3" runat="server"

Text="Введите пароль:" Width="140px"></asp:Label>

          </td>

          <td style="width: 100px">

            <asp:TextBox ID="txtPassword1" runat="server"

TextMode="Password" Width="145px" />

          </td>

          <td style="width: 29px">

            <asp:RequiredFieldValidator

ID="RequiredFieldValidator2" runat="server" ErrorMessage="Пароль

не должен быть пустым"

  ControlToValidate="txtPassword1">*</asp:RequiredFieldValidator>

          </td>

        </tr>

        <tr>

          <td style="width: 100px">

            <asp:Label ID="Label4" runat="server" Text="Повторите

пароль" Width="140px"></asp:Label>

          </td>

          <td style="width: 100px">

            <asp:TextBox ID="txtPassword2" runat="server"

TextMode="Password" Width="145px" />

          </td>

          <td style="width: 34px" colspan="3">

            <asp:CompareValidator ID="CompareValidator1"

runat="server" ErrorMessage="Пароли должны совпадать!"

              ControlToValidate="txtPassword1"

ControlToCompare="txtPassword2">*</asp:CompareValidator>

          </td>

        </tr>

        <tr>

          <td style="width: 300px; height: 49px;" colspan="3">

            <asp:ValidationSummary ID="ValidationSummary1"

runat="server" ShowMessageBox="True"

              Width="349px" />

          </td>

        </tr>

      </table>

    </asp:WizardStep>

    <asp:WizardStep ID="Step2" runat="server" Title="Контактная

информация">

      <table style="width: 336px; height: 199px">

        <tr>

          <td style="width: 100px">

            <asp:Label ID="Label5" runat="server" Text="Введите

e-mail"></asp:Label>

          </td>

          <td style="width: 100px">

            <asp:TextBox ID="TextBox1"

runat="server"></asp:TextBox>

          </td>

        </tr>

        <tr>

          <td style="width: 100px; height: 21px">

            <asp:RequiredFieldValidator

ID="RequiredFieldValidator3" runat="server" ErrorMessage="Введите

e-mail"

       ControlToValidate="TextBox1"></asp:RequiredFieldValidator>

          </td>

          <td style="width: 100px; height: 21px">

            <asp:RegularExpressionValidator

ID="RegularExpressionValidator1" runat="server" ErrorMessage="Не-

правильный адрес"

              ValidationExpression="\w+([-+.']\w+)*@\w+([-

.]\w+)*\.\w+([-.]\w+)*"

ControlToValidate="TextBox1"></asp:RegularExpressionValidator>

          </td>

        </tr>

        <tr>

          <td style="width: 100px; height: 21px">

            <asp:Label ID="Label6" runat="server" Text="Введите

номер телефона"></asp:Label>

          </td>

          <td style="width: 100px; height: 21px">

            <asp:TextBox ID="TextBox2"

runat="server"></asp:TextBox>

          </td>

        </tr>

        <tr>

          <td colspan="2">

            <asp:RequiredFieldValidator

ID="RequiredFieldValidator4" runat="server" ErrorMessage="Введите

номер телефона"

       ControlToValidate="TextBox2"></asp:RequiredFieldValidator>

          </td>

        </tr>

      </table>

    </asp:WizardStep>

    <asp:WizardStep ID="Step3" runat="server" Title="Подтвержде-

ние">

      <table>

        <tr>

          <td style="width: 100px">

            <asp:Label ID="Label7" runat="server"

Text="Имя"></asp:Label>

          </td>

          <td style="width: 100px">

           <asp:Label ID="Label8" runat="server"

Text="Label"></asp:Label>

          </td>

        </tr>

        <tr>

         <td style="width: 100px">

            <asp:Label ID="Label9" runat="server" Text="E-

mail"></asp:Label>

          </td>

          <td style="width: 100px">

            <asp:Label ID="Label10" runat="server"

Text="Label"></asp:Label>

          </td>

        </tr>

        <tr>

          <td style="width: 100px">

            <asp:Label ID="Label11" runat="server" Text="Номер

телефона"></asp:Label>

          </td>

          <td style="width: 100px">

            <asp:Label ID="Label12" runat="server"

Text="Label"></asp:Label>

          </td>

        </tr>

      </table>

    </asp:WizardStep>

  </WizardSteps>

  <StepStyle BorderWidth="0px" ForeColor="#5D7B9D" />

  <SideBarStyle BackColor="#7C6F57" BorderWidth="0px" Font-

Size="0.9em" VerticalAlign="Top" />

  <NavigationButtonStyle BackColor="#FFFBFF" BorderColor="#CCCCCC"

BorderStyle="Solid"

    BorderWidth="1px" Font-Names="Verdana" Font-Size="0.8em"

ForeColor="#284775" />

  <SideBarButtonStyle BorderWidth="0px" Font-Names="Verdana"

ForeColor="White" />

  <HeaderStyle BackColor="#5D7B9D" BorderStyle="Solid" Font-

Bold="True" Font-Size="0.9em"

    ForeColor="White" HorizontalAlign="Left" />

</asp:Wizard>

Для того чтобы на третьем шаге отображались данные, собранные в предыдущих шагах, перехватывается событие ActiveStepChanged:

    protected void Wizard1_ActiveStepChanged(object sender,

EventArgs e)

    {

        if (Wizard1.ActiveStep.ID == "Step3")

        {

            Label8.Text =

     ((TextBox)Wizard1.Controls[0].FindControl("txtName")).Text;

            Label10.Text =

     ((TextBox)Wizard1.Controls[0].FindControl("TextBox1")).Text;

            Label12.Text =

     ((TextBox)Wizard1.Controls[0].FindControl("TextBox2")).Text;

 

        }

    }

Если у шага свойство AllowReturn установлено в False, то возврат к этому шагу будет невозможен. Другое интересное свойство — StepType. Оно позволяет заменить данный по умолчанию набор кнопок навигации. Шаг, у которого StepType равен Complete, не виден в боковой панели и будет показан после того, как в последнем шаге будет нажат Finish:

  <asp:WizardStep Runat="server" Title="Final Step"

StepType="Complete">

Спасибо за регистрацию.</asp:WizardStep>

В данном шаге какие-либо кнопки отсутствуют.

Использование JavaScript

В лекции 1 мы создали страницу, которая показывала время на сервере. Если пользователь находится в другом часовом поясе, время на его часах будет другое. Можно ли ее переделать, чтобы время совпадало с часами клиента? Ответ на этот вопрос — положительный. В страницу можно встроить код на JavaScript, который будет работать при загрузке страницы. Текст у метки менять нельзя, поэтому используем TextBox. Так как он — только для чтения и ширина границы равна 0, отличить его от метки сложно:

<%@ Page Language="C#" AutoEventWireup="true" 
CodeFile="time.aspx.cs" Inherits="time" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Время у клиента</title>
</head>
<body 
onload="javascript:document.forms[0]['ClientTime'].value=Date();">
    <form id="form1" runat="server">
        <div>
            <asp:TextBox ID="ClientTime" runat="server" 
BorderWidth="0px" ReadOnly="True" 
Width="500px"></asp:TextBox><br />
            <input type="button" id="Button1" runat="server" 
value="Узнать время" onclick="Show()" />
        </div>
    </form>
</body>
</html>

Свойство класса Page ClientScript позволяет определять для страницы клиентские сценарии. Метод RegisterClientScriptBlock задает скрипт, который будет встроен в текст страницы:

    protected void Page_Load(object sender, EventArgs e)
    {
        string myScript = @"function Show() { 
document.forms[0]['ClientTime'].value=Date(); }";
        if 
(!Page.ClientScript.IsClientScriptBlockRegistered("MyScript"))
           Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
"MyScript", myScript, true);
}
 
<input type="button" ID="Button1"  runat ="server" value="Узнать 
время"
OnClick="Show()"/>

Первый аргумент — тип данной страницы, второй — идентификатор скрипта, который позволит отличить его от других скриптов, третий — текст сценария. Четвертый параметр — булевский, если он равен True, то теги <script type="text/javascript"> и </script> будут автоматически окружать текст функции. Страница, которая получится, обновляет время при каждом нажатии на кнопку.

Ее HTML-код выглядит так:

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1"><title>
 
</title></head>
<body 
onload="javascript:document.forms[0]['ClientTime'].value=Date();">
    <form name="form1" method="post" action="Default2.aspx" 
id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
value="/wEPDwUJMjcxMzU0ODE3ZGQKqi3Rssxd/mXLs5G1HpFSaJ/j1A==" />
</div>
 
 
<script type="text/javascript">
<!--
function Show() { document.forms[0]['ClientTime'].value=Date(); 
}// -->
</script>
 
        <div>
            <input name="ClientTime" type="text" readonly="read-
only" id="ClientTime" style="border-width:0px;width:500px;" /><br 
/>
            <input name="Button1" type="button" id="Button1" 
value=" " onclick="Show()" />
        </div>
    
<div>
 
   <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALI-
DATION" value="/wEWAwLS+f/WBwK8i8+nDwKM54rGBkVGyzaTKHVi8uFS3xL8ule0VqeH" 
/>
</div></form>
</body>
</html>

Скрипт заключен в комментарии, чтобы все работало на старых браузерах. Он включен в текст до элементов управления.

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

string myScript1 = 
@"alert(document.forms[0]['ClientTime'].value);";
Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
"AlertScript", myScript1, true);

Значение поля в момент отображения скрипта еще не определено. Поэтому нужно вызывать RegisterStartupScript:

string myScript1 = @"function Message() 
{alert(document.forms[0]['ClientTime'].value);}";
Page.ClientScript.RegisterStartupScript(this.GetType(),
"AlertScript", myScript1, true);

Метод RegisterClientScriptInclude позволяет подключить внешний файл JavaScript. Например,

Page.ClientScript.RegisterClientScriptInclude("myKey", 
"ExternJavaScriptCode.js");

создает на выданной странице код

<script src="ExternJavaScriptCode.js" 
type="text/javascript"></script>

В этих примерах мы использовали не серверные командные кнопки, а элементы управления HTML. Причина заключается в том, что нажатие на командную кнопку отправляет форму на сервер. Событие OnClick выполняется на сервере. А в JavaScript существует свой OnClick. Как же его вызвать? Свойство Attributes позволяет обратиться к атрибутам элемента, даже тем, которые не соответствуют встроенным свойствам:

<asp:Button ID="Button2" runat="server" Text="Button" />
protected void Page_Load(object sender, EventArgs e)
{
    Button2.Attributes.Add("onclick", "Show();return false");
}

return false нужно писать обязательно, иначе форма будет отправлена на сервер.

Эти функции можно применить к любым серверным элементам:

  public static void AddConfirmMessage(WebControl ctl, string 
message)
  {
    ctl.Attributes.Add("onclick", "if ( ! confirm( '"
       + message + "' )) return false; ");
  }
 
  public static void AddPopupMessage(WebControl ctl, string mes-
sage)
  {
    ctl.Attributes.Add("onclick", "alert( '" + message + "'); ");
}

Callback и его отличие от Postback

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

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

  1. Init
  2. Load State
  3. Process Postback Data
  4. Load
  5. Postback Events
  6. Save State
  7. PreRender
  8. Render
  9. Unload

В обычной ситуации постбэка событие на странице, например, щелчок на кнопке формы, вызывает отправку запроса HTTP POST web-серверу. Сервер обрабатывает запрос обработчиком IPostbackEvent Handler и запускает весь жизненный цикл страницы заново. Она загружает состояние страницы, обрабатывает введенные данные, обрабатывает события отправки, сохраняет состояние страницы, генерирует страницу заново и отправляет ее браузеру клиента. Страница полностью перезагружается, это занимает время и использует дополнительный трафик.

С другой стороны, можно задействовать возможность обратного вызова, как показано на рисунке.

В этом случае событие (например, нажатие на кнопку) вызывает выполнение скрипта-обработчика у клиента (функция JavaScript), который посылает асинхронный запрос web-серверу.

На сервере обработчик ICallbackEventHandler пропускает запрос через процесс, похожий на тот, который используется при постбэке, но некоторые крупные этапы этого процесса (например, отрисовка страницы) пропускаются. После того как информация загружена, результат возвращается к объекту, вызвавшему обратный вызов. Код скрипта вставляет эти данные в web-страницу, используя возможности JavaScript делать это без обновления страницы. Количество фаз жизненного цикла сокращается до 6:

  1. Init
  2. Load State
  3. Process Postback Data
  4. Load
  5. CallBack Event
  6. Unload

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

Простой пример использования Callback

Посмотрим на простую страницу ASP .NET, которая использует эту возможность. В этом примере на странице есть кнопка HTML и серверный элемент управления TextBox.

Идея примера в том, что когда пользователь щелкает на кнопке на форме, запускается служба обратного вызова и в текстовое поле записывается случайное число:

<%@ Page Language="C#" AutoEventWireup="true"

CodeFile="SimpleCallback.aspx.cs" Inherits="SimpleCallback" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

  <title>Callback Page</title>

 

  <script type="text/javascript">

    function GetNumber(){

      UseCallback();

    }

    function GetRandomNumberFromServer(TextBox1, context){

      document.forms[0].TextBox1.value = TextBox1;

    }

  </script>

 

</head>

<body>

  <form id="form1" runat="server">

    <div>

      <input id="Button1" type="button" value="Случайное число"

onclick="GetNumber()" />

      <br />

      <br />

      <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>

    </div>

  </form>

</body>

</html>

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

Запускает весь процесс функция JavaScript GetNumber — обработчик щелчка на кнопке.

Функция GetRandomNumberFromServer() находится на обратном конце процесса и записывает результат обратного вызова в Textbox1.

Класс этой страницы не только наследуется от Page, но и реализует интерфейс System.Web.UI.ICallbackEventHandler:

public partial class SimpleCallback : System.Web.UI.Page,

System.Web.UI.ICallbackEventHandler

{

  private string _callbackResult = null;

  protected void Page_Load(object sender, EventArgs e)

  {

    string cbReference =

Page.ClientScript.GetCallbackEventReference(this,

    "arg", "GetRandomNumberFromServer", "context");

    string cbScript = "function UseCallback(arg, context)" +

    "{" + cbReference + ";" + "}";

    Page.ClientScript.RegisterClientScriptBlock(this.GetType(),

    "UseCallback", cbScript, true);

  }

  public void RaiseCallbackEvent(string eventArg)

  {

    Random rnd = new Random();

    _callbackResult = rnd.Next().ToString();

  }

  public string GetCallbackResult()

  {

    return _callbackResult;

  }

}

В этом интерфейсе определены 2 метода, которые необходимо реализовать, — RaiseCallbackEvent и GetCallbackResult, оба работают с запросами клиентских скриптов.

RaiseCallbackEvent позволяет получить со страницы данные, правда, только строкового типа. GetCallbackResult возвращает результат клиентскому скрипту, в данном случае GetRandomNumberFromServer — именно она зарегистрирована в качестве получателя результата.

Обработчик Page_Load включает создание функции, которая будет управлять запросами и ответами на стороне клиента.

Функция, которая ставится на клиенте для возможности обратного вызова, называeтся UseCallback(). Эта строка затем помещается на web-страницу, используя функцию Page.ClientScript.RegisterClientScriptBlock. Убедитесь, что имя, которое вы применяете здесь, такое же, как и в клиентской функции JavaScript:

<script type="text/javascript">

<!--

function UseCallback(arg, context)

{

WebForm_DoCallback('__Page',arg,GetRandomNumberFromServer,con-

text,null,false);

}

// -->

</script>

В результате получается страница, которая при нажатии на кнопку обновляет содержание текстовой строки, но не отрисовывает всю страницу. Есть лишь одно ограничение — здесь используется XmlHTTP и поэтому клиентский браузер должен его поддерживать (Internet Explorer 6.0, Mozilla FireFox 1.5 и Opera 9.0 поддерживают). Так это или нет, можно узнать с помощью свойств SupportsCallBack и SupportsXmlHTTP:

if (Page.Request.Browser.SupportsXmlHTTP == true)

{

}

Callback с параметрами

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

Страница просит выбрать город и вызывает серверный скрипт, чтобы запустить запрос к XML web-сервису на сервере. Web-сервис возвращает прогноз погоды для данной местности в текстовом формате:

<%@ Page Language="C#" AutoEventWireup="true" 
CodeFile="Callback.aspx.cs" Inherits="Callback" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Web Service Callback с параметром</title>
    <script type="text/javascript">
    function GetTemp()
    {
      var City = document.forms[0].DropDownListCity.value;
      UseCallback(City, "");
    }
    function GetTempFromServer(TextBox2, context)
    {
      document.forms[0].TextBox2.value = "В городе " +
      document.forms[0].DropDownListCity.value + " температура 
воздуха " + TextBox2;
    }
</script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <asp:DropDownList ID="DropDownListCity" runat="server">
      </asp:DropDownList>
      <br />
      <input id="Button1" type="button" value="Get Temp" 
onclick="GetTemp()"/><br />
      <asp:TextBox ID="TextBox2" runat="server" Width="634px" 
BackColor="#FFFFC0" Height="34px" 
ReadOnly="True"></asp:TextBox></div>
    </form>
</body>
</html>
 
using System;
using System.Xml;
 
public partial class Callback : System.Web.UI.Page,
System.Web.UI.ICallbackEventHandler
{
  string _callbackResult;
  protected void Page_Load(object sender, EventArgs e)
  {
    GlobalWeather ws = new GlobalWeather();
    string results = ws.GetCitiesByCountry("Russia");
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(results);
    XmlNode child = doc.ChildNodes[0];
    foreach (XmlElement el in child.ChildNodes)
    {
      XmlElement city = el["City"];
      DropDownListCity.Items.Add(city.InnerText);
    }
    string cbReference = 
Page.ClientScript.GetCallbackEventReference(this,
    "arg", "GetTempFromServer", "context");
    string cbScript = "function UseCallback(arg, context)" +
    "{" + cbReference + ";" + "}";
    Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
    "UseCallback", cbScript, true);
  }
  public void RaiseCallbackEvent(string eventArg)
  {
    GlobalWeather ws = new GlobalWeather();
    _callbackResult = ws.GetWeather(eventArg, 
"Russia").ToString();
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(_callbackResult);
    XmlNode child = doc.ChildNodes.Item(1);
    XmlElement el = child["Temperature"];
    _callbackResult = el.InnerText;
  }
  public string GetCallbackResult()
  {
    return _callbackResult;
  }
}

Для работы этого примера нужно создать web-ссылку на сервис "http://www.webservicex.net/globalweather.asmx". Как работать с web-сервисами, описывалось в лекции 15.

Разница с клиентским обратным вызовом в том, что этот пример отправляет функции обратного вызова параметр.

Это делается в функции JavaScript GetTemp() на странице .aspx:

    function GetTemp()
    {
      var City = document.forms[0].DropDownListCity.value;
      UseCallback(City, "");
    }

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

Заключение

В этом курсе мы затронули только часть возможностей ASP .NET 2.0. В последнее время широко пропагандируется технология Ajax и основанная на ней технология Atlas от Microsoft. Объявлено о выходе .NET Framework 3.0. Поэтому и мне, и вам предстоит еще многому научиться.

 

www.4its.ru