Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад] [Содержание] [Дальше]

Создание приложений с базами данных для Интернета и интрасетей: практическое руководство

(С) Александр Вячеславович Фролов, Григорий Вячеславович Фролов, 2000

4. Связь приложений с базами данных через ADO

4. Связь приложений с базами данных через ADO.. 1

Основы ADO.. 2

Программная модель ADO.. 2

Установка соединения. 3

Подготовка команды и параметров. 3

Выполнение команды.. 3

Обработка результатов выполнения команды.. 3

Обработка ошибок. 3

Объекты ADO.. 3

Объект Connection. 3

Объект Command. 5

Объект Parameters. 6

Объект Recordset. 8

Объект Errors. 10

Объект Properties. 13

Константы ADO.. 13

Проект Интернет-магазина.. 14

Создание базы данных. 14

Подготовка таблиц. 14

Таблица managers. 15

Таблица clients. 16

Таблица books. 18

Таблица orders. 19

Подготовка хранимых процедур. 20

Создание источника данных. 21

Подготовка виртуальных каталогов сервера Web. 26

Виртуальный каталог приложения покупателя BookShopClient 26

Виртуальный каталог административного приложения BookShop. 26

Приложение покупателя. 29

Файл global.asa. 34

Страницы входа и начальной регистрации. 35

Вход зарегистрированных посетителей. 35

Обработка ошибок. 37

Хранимая процедура ClientLogin. 39

Определение фреймов главной страницы.. 39

Страница меню команд. 40

Страница просмотра списка книг. 40

Добавление книги в корзину. 42

Страница просмотра содержимого корзины.. 42

Удаление книги из корзины.. 45

Административное приложение. 46

Файл global.asa. 56

Страницы входа. 56

Главная страница. 58

Страница меню команд. 58

Страница с сообщением о подключении. 58

Страницы управления персоналом. 59

Просмотр списка сотрудников. 59

Создание новой записи. 60

Удаление учетной записи сотрудника. 62

Редактирование записи сотрудника. 63

Редактирование списка книг. 64

Просмотр списка книг. 65

Добавление новой книги. 66

Удаление книги. 67

Редактирование описания книги. 68

Работа с записями покупателей. 70

Форма поиска покупателей. 71

Просмотр списка зарегистрированных покупателей. 72

Удаление записи покупателя. 76

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

Редактирование регистрационных данных покупателя. 80

Работа с ADO в приложениях C++. 83

Импортирование библиотеки типов ADO.. 84

Обращение к интерфейсам и методам ADO.. 85

Инициализация COM... 85

Установка соединения с источником данных. 85

Выполнение команды.. 86

Работа с набором записей. 87

Вызов хранимой процедуры.. 87

Обработка ошибок. 89

Пример программы.. 90

Функция login. 91

Функция getManagers. 93

Вызов ADO через функции Win32. 95

Обращение к интерфейсам и методам ADO.. 95

Инициализация COM и переменных BSTR.. 95

Установка соединения с источником данных. 95

Выполнение команды.. 96

Работа с набором записей. 97

Пример программы.. 98

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

Существуют различные способы связи приложений с базами данных. На сегодняшний день наиболее перспективный из них, несомненно, связан с применением интерфейса ActiveX Data Objects (ADO). Посредством этого интерфейса приложения (как обычные, так и ориентированные на использование технологий Интернета) могут подключаться к базам данных, извлекать, обрабатывать и обновлять информацию в них.

Эта глава посвящена применению интерфейса ADO для связи приложений ASP с базами данных, созданными на основе Microsoft SQL Server. В качестве примера приложения Web мы выбрали Интернет-магазин, торгующий книгами.

Основы ADO

Напомним, что в первой главе книги уже упоминалось, что для доступа к базам данных SQL Server можно использовать различные методы — программный интерфейс DB Library, программный интерфейс ODBC, объектный интерфейс RDO, объектный интерфейс OLE DB и, наконец, объектный интерфейс ADO.

ADO представляет собой интерфейс уровня приложений, созданный поверх объектного интерфейса OLE DB. При этом интерфейс OLE DB обеспечивает универсальный доступ к данным. Такой доступ обеспечивается в свою очередь с помощью провайдеров, таких, как Microsoft OLE DB Provider для ODBC (MSDASQL) или Microsoft OLE DB Provider для SQL Server (SQLOLEDB).

Программная модель ADO

Ключевыми элементами программной модели ADO является набор объектов, с помощью которых осуществляется соединение с базами данных, выполнение команд с параметрами, получение результата выполнения этих команд в виде переменных или наборов записей, обработка событий и ошибок.

Рассмотрим порядок обращения приложения к базе данных с применением программной модели ADO.

Установка соединения

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

Данный объект позволяет установить соединение с источником данных посредством интерфейса ODBC или напрямую. В первом случае Вам надо указать имя источника данных Data Source Name (DSN), а во втором — информацию об источнике данных: имя драйвера, имя сервера, пароль и т. д. В примерах мы будем использовать подключение к источнику данных с применением DSN.

После завершения работы с соединением его необходимо закрыть, вызвав метод Close объекта Connection.

Подготовка команды и параметров

После установки соединения приложение должно подготовить объект Command, записав в его свойства команды, необходимые для доступа к данным. Это могут быть команды выполнения строк языка Transact-SQL (например, строки «select * from clients»), команда вызова хранимой процедуры SQL Server по ее имени или имя таблицы.

При помощи объекта Parameter приложение может передать вместе с командой параметры. Входные параметры позволяют передавать информацию в хранимые процедуры SQL Server, а выходные — принимать информацию из хранимой процедуры.

Выполнение команды

Один из методов объекта Command с именем Execute предназначен для инициирования выполнения команды.

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

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

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

Например, в результате выполнения команды SQL «select * from clients» создается набор записей Recordset, представляющих собой массив строк таблицы clients. Приложение способно просмотреть все записи из набора, сохранить их в своей локальной памяти или использовать каким-либо другим способом.

В частности, Вы получаете право обновить полученный набор записей, если хотите обновить источник данных.

После обработки набора записей его нужно закрыть методом Close, предусмотренным для этой цели в объекте Recordset.

Обработка ошибок

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

Объекты ADO

А сейчас мы расскажем о методах и свойствах объектов, составляющих фундамент ADO. Применение этих методов иллюстрируется в серверных сценариях ASP, составленных на JScript.

Объект Connection

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

Объект Connection связан с объектами Errors, Command и Recordset, как это показано на рис. 4-1.

Рис. 4-1. Объект Connection

Команды Command имеют отношение к вполне конкретному источнику данных, открытому для объекта Connection. Таким образом, Вы создаете объект Command для использования с выбранным источником данных.

После успешного выполнения команды может быть создан набор записей Recordset. Он тоже генерируется в контексте того сеанса связи с источником данных, который был открыт в рамках объекта Connection.

Если при выполнении команды возникли ошибки, создается объект Errors, представляющий собой набор (collection) объектов Error. Все эти объекты имеют отношение к конкретному объекту Connection и должны обрабатываться в его контексте.

Рассмотрим маленький пример — фрагмент серверного сценария JScript, расположенного на странице ASP:

var connect;
connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout = 10;
connect.Open("DSN=BookStore", "dbo", "password");

В первой строке мы определяем переменную connect, предназначенную для хранения объекта Connection.

Далее мы создаем объект Connection, вызывая метод CreateObject объекта Server (объект Server является встроенным объектом ASP).

Перед тем как установить соединение с источником данных, мы задаем два свойства объекта Connection — тайм-аут сеанса ConnectionTimeout и тайм-аут выполнения команды CommandTimeout. Первое из них определяет время ожидания установления канала связи с источником данных (в секундах), а второе — время ожидания выполнения команды. Если тайм-аут истек, устанавливается состояние ошибки.

Зачем задаются эти параметры?

Просто для того, чтобы сервер Web не бесконечно ожидал соединения или выполнения команды. Соединение будет разорвано также и в случае сильной загрузки сервера SQL Server, когда он не успевает справиться с поступающими запросами. Зная среднее время выполнения команд, Вы можете в своем приложении соответственным образом настроить тайм-ауты.

Помимо свойств ConnectionTimeout и CommandTimeout, объект Connection имеет и другие свойства, определяющие параметры соединения. Однако пока мы ограничимся применением только этих свойств.

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

Чтобы закрыть канал связи, используйте метод Close объекта Connection. Все неиспользованные каналы связи следует закрывать для экономии ресурсов сервера. В частности, сервер SQL Server может иметь ограниченное количество лицензий на соединения с клиентами. Если вовремя не закрывать неиспользуемые соединения, можно быстро исчерпать лимит таких лицензий, в результате чего приложение перестанет работать.

Объект Command

Как мы уже говорили, объект Command посылает команды в базу данных с целью проведения таких операций, как запуск хранимых процедур или исполнения строк программы Transact-SQL.

Прежде всего, необходимо создать объект Command, обратившись для этого к методу CreateObject объекта Server:

var cmd;
cmd = Server.CreateObject("ADODB.Command");

Как видите, объект Command создается аналогично объекту Connection.

После создания объекта Command необходимо установить как минимум три свойства этого объекта — ActiveConnection, CommandText и CommandType:

cmd.ActiveConnection = connect;
cmd.CommandType      = adCmdStoredProc;
cmd.CommandText      = "ClientLogin";

Сначала мы расскажем о свойстве ActiveConnection.

Вы уже знаете, что любой объект Command имеет отношение к конкретному соединению Connection. Чтобы указать, что команда предназначена для источника данных, доступ к которому выполняется через соединение connect, необходимо записать ссылку на объект Connection в свойство ActiveConnection.

Свойство CommandType задается как константа и определяет тип выполняемой команды. При этом назначение свойства CommandText полностью определяется типом команды, как это показано в таблице 4-1.

Таблица 4-1. Константы типов команд

Константа

Назначение свойства CommandText

adCmdText

Текстовое определение команды

adCmdTable

Имя таблицы

adCmdStoredProc

Имя хранимой процедуры SQL Server

adCmdUnknown

Неизвестный тип команды (установлен по умолчанию). Если тип команды явно не указан, ADO обращается к провайдеру с запросом на определение типа команды. Выполнение этого запроса, связанное с анализом свойства adCmdText, отнимает время. Поэтому для увеличения производительности рекомендуется указывать тип команды явным образом

Чтобы выполнить такую строку программы SQL, как «select * from clients», следует записать в свойство CommandType константу adCmdText, а в свойство CommandText — строку программы SQL, например:

cmd.CommandType  = adCmdText;
cmd.CommandText = "select * from clients";

Ранее приведен пример использования константы adCmdStoredProc для запуска хранимой процедуры.

В наших приложениях мы будем выполнять все обращения к базе данных исключительно с применением хранимых процедур. Таким образом, в серверных сценариях, расположенных на наших страницах ASP, Вы не найдете ни одной строки SQL. Однако в разделе, посвященном вызову методов ADO в приложениях C++, показан пример программы, которая непосредственно запускает строку SQL, выполняющую выборку данных из таблицы.

А теперь небольшое отступление от темы: мы объясним, почему отказались от кодирования программ SQL непосредственно в серверных сценариях (хотя это вполне допустимо).

Главная причина этого — стремление отделить данные от программ.

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

Объект Parameters

С командой можно передать один или несколько параметров. Параметры передаются в виде набора Parameters, содержащего объекты Parameter (рис. 4-2).

Рис. 4-2. Набор Parameters

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

cmd.Parameters.Append(cmd.CreateParameter(
  "User", adVarChar, adParamInput, 50, "admin"));
 
cmd.Parameters.Append(cmd.CreateParameter(
  "Pass", adVarChar, adParamInput, 50, "adm_password"));
 
var ParamOut = cmd.CreateParameter(
  "Rights", adVarChar, adParamOutput, 50, " ");
 
cmd.Parameters.Append(ParamOut);

В первой строке мы обращаемся к методу CreateParameter, определенному в объекте cmd класса Command (напомним, параметры имеют отношение к командам).

Через первый параметр метода CreateParameter передается имя параметра команды cmd. В нашем случае это User — имя пользователя.

Второй параметр метода CreateParameter определяет тип параметра команды cmd (строка, число, время, деньги и т. д.) и задается в виде константы. Мы передаем в хранимую процедуру имя пользователя типа varchar, поэтому тип параметра указан как adVarChar. Другие типы параметров приведены в таблице 4-2.

Таблица 4-2. Константы для указания типов параметра команды

Константа

Тип данных

adArray

Эту константу можно складывать с другими при помощи операции ИЛИ для указания, что параметр содержит массив указанных типов данных

adBigInt

Знаковое целое длиной 8 бит

adBinary

Бинарное значение (байт)

adBoolean

Булево значение

adByRef

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

adBSTR

Строка Unicode, закрытая двоичным нулем (тип BSTR)

adChar

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

adCurrency

Денежная сумма. Этот тип данных представляет собой 8-байтовое знаковое целое. Хранит значение с четырьмя цифрами справа от десятичной точки

adDate

Дата. Хранится в формате double. Целая часть представляет собой количество дней, прошедшее с 30 декабря 1899 года, а дробная — дробную часть текущего дня

adDBDate

Дата в формате ГГГГММДД, где:
ГГГГ — год;
ММ — месяц;
ДД — день

adDBTime

Время в формате ЧЧММСС, где:
ЧЧ — часы;
ММ — минуты;
СС — секунды

adDBTimeStamp

Отметка о времени (date-time stamp) в формате ГГГГММДДЧЧММСС плюс дробная часть

adDecimal

Десятичное целое с фиксированной точкой

adDouble

Значение двойной точности с плавающей точкой

adEmpty

Пустое (не заполненное) значение

adError

Код ошибки (32-разрядный)

adGUID

Глобальный уникальный идентификатор (GUID)

adIDispatch

Указатель на интерфейс IDispatch объекта OLE

adInteger

Четырехбайтовое знаковое целое

adIUnknown

Указатель на интерфейс IUnknown объекта OLE

adLongVarBinary

Тип long (только для объекта Parameter)

adLongVarChar

Строка переменной длины (только для объекта Parameter)

adLongVarWChar

Строка переменной длины, состоящая из многобайтовых символов (только для объекта Parameter)

adNumeric

Численное значение с фиксированной точностью

adSingle

Плавающее значение с одинарной точностью

adSmallInt

Двухбайтовое знаковое целое

adTinyInt

Однобайтовое знаковое целое

adUnsignedBigInt

8-байтовое беззнаковое целое

 

adUnsignedInt

4-байтовое беззнаковое целое

adUnsignedSmallInt

2-байтовое беззнаковое целое

adUnsignedTinyInt

Однобайтовое беззнаковое целое

adUserDefined

Переменная, определенная пользователем

adVarBinary

Бинарное значение (только для объекта Parameter)

adVarChar

Строка символов (только для объекта Parameter)

adVariant

Тип автоматизации Variant

adVector

Эта константа складывается с другими, если данные имеют структуру DBVECTOR, определенную в OLE DB, которая содержит счетчик элементов и указатель на данные другого типа

adVarWChar

Строка Unicode, закрытая двоичным нулем (только для объекта Parameter)

adWChar

Строка Unicode, закрытая двоичным нулем

Третий параметр метода CreateParameter задает направление передачи данных через соответствующий параметр команды cmd. Параметры User и Pass входные, а Rights — выходной. Для обозначения входных параметров используется константа adParamInput. Выходные параметры обозначаются константой adParamOutput.

Полный список констант направления передачи данных приведен в таблице 4-3.

Таблица 4-3. Константы для указания направления передачи данных

Константа

Направление передачи данных

AdParamInput

Входной параметр. Используется по умолчанию

AdParamOutput

Выходной параметр

AdParamInputOutput

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

AdParamReturnValue

Через этот параметр передается возвращаемое значение

Через последние два параметра методу CreateParameter передаются соответственно размер области памяти, занимаемой параметром, и значение этого параметра. В нашем случае все параметры представляют собой текстовые строки размером не более 50 байт. Параметры User и Pass имеют значение admin и adm_password соответственно, а параметр Rights задается как символ пробела (это выходной параметр).

После создания очередного параметра класса Parameter его нужно добавить в набор Parameters. Эта задача выполняется при помощи метода Append, определенном в объекте Parameters.

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

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

Это делается простым обращением к свойству value параметра:

cmd.Execute();
var sRights=ParamOut.value

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

Объект Recordset

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

Когда Вы запускаете команду методом Execute, метод возвращает Вам объект Recordset, созданный в результате ее выполнения (конечно, если данная команда генерирует упомянутую выше таблицу).

Объект Recordset — это набор, состоящий из набора Fields. Последний, в свою очередь, состоит из объектов Field (рис. 4.3).

Рис. 4-3. Набор Recordset

Вы можете извлечь любую запись из набора Recordset, пользуясь набором Fields и объектом Field.

Как это сделать?

При создании объекта Recordset автоматически создается курсор, связанный таблицей, полученной в результате выполнения команды. Методы объекта Recordset позволяют определять тип курсора (предназначенный только для чтения, допускающий просмотр в одном направлении, статический, динамический, управляемый ключами), а также перемещать курсор для извлечения полей таблицы.

Предположим, нам нужно отобразить на странице ASP список книг, отобранных посетителем Интернет-магазина. Мы подготовили хранимую процедуру, получающую в качестве входного параметра имя клиента ClientID, и возвращающего список отобранных книг как результат выполнения запроса оператором SELECT:

CREATE PROCEDURE ListOrders @ClientID varchar(50) AS
DECLARE @nClientID INT
SELECT @nClientID=clients.ClientID FROM clients WHERE UserID=@ClientID
SELECT books.booksID, books.Author, books.Title, books.Publisher, books.Price FROM orders
JOIN books ON orders.booksID=books.booksID
WHERE orders.ClientID=@nClientID

Эта процедура будет подробно рассмотрена позже. Сейчас нам важно только то, что она получает один входной параметр ClientID, а возвращает таблицу, содержащую пять столбцов таблицы books: booksID, Author, Title, Publisher и Price (это как раз тот интерфейс между приложением и базой данных, который нужен для отделения программы от данных).

Для вызова хранимой процедуры ListOrders мы используем следующий фрагмент серверного сценария:

var connect, rs, cmd, ClientID;

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout = 10;
connect.Open("DSN=BookStore", "dbo", "password");
 
cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "ListOrders";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;
 
cmd.Parameters.Append(cmd.CreateParameter(
  "ClientID", adVarChar, adParamInput, 50, ClientID));

rs = cmd.Execute();

Обратите внимание на последнюю строчку: значение, возвращенное методом Execute, присваивается переменной rs. Эта переменная хранит объект Recordset, созданный в результате выполнения хранимой процедуры ListOrders.

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

var fieldbooksID   = 0;
var fieldAuthor    = 1;
var fieldTitle       = 2;
var fieldPublisher = 3;
var fieldPrice       = 4;

Теперь мы должны получить все строки возвращенной таблицы, передвигая курсор и обращаясь к объектам Field:

%>
<HTML>
<BODY>
<h2>Вы отобрали для покупки</h2>
<TABLE BORDER=1>
<%
while (!rs.EOF)
{%>
<tr>
  <td>
     <%=rs.Fields(fieldAuthor)%>. <%=rs.Fields(fieldTitle)%><br>
     <%=rs.Fields(fieldPublisher)%></td>
  <td>
     <%=rs.Fields(fieldPrice)%>
у.е.</td>
</tr>
<%
  rs.MoveNext();
}
%>
</TABLE>
<%

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

Перемещение курсора на следующую запись выполняется методом MoveNext, определенным в объекте Recordset. Чтобы проверить условие завершения цикла, наш сценарий обращается к свойству rs.EOF.

Таким образом, при каждом перемещении курсора мы получаем доступ к очередной строке таблицы, созданной в результате вызова хранимо процедуры ListOrders. Для извлечения содержимого отдельных полей текущей строки мы используем набор Fields. Элементы этого набора (представляющие собой объекты Field) соответствуют полям текущей строки: первый элемент (с индексом 0) соответствует первому столбцу (booksID), второй — второму и т. д.

Заметим, что во многих случаях удобнее обращаться к полям набора записей не по номерам колонок, а по именам:

<td><%=rs.Fields("Author")%>. <%=rs.Fields("Title")%>
<br><%=rs.Fields("Publisher")%></td>
<td><%=rs.Fields("Price")%>
у.е.

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

Объект Errors

При выполнении команд могут возникать ошибки. Ошибки попадают в ADO от провайдера и помещаются в набор Errors. Заметим, что в зависимости от ситуации в результате выполнения одной команды может возникать сразу несколько ошибок. Для каждой создается объект Error, который затем помещается в набор Errors.

В случае серверных сценариев JScript объект Error имеет свойства number и description, первое из которых содержит числовой код ошибки, а второе — ее текстовое описание (рис. 4-4).

Заметим, что в сценариях VBScript объект Error имеет несколько свойств, перечисленных в таблице 4-4. Эти же свойства доступны и в приложениях C++, обращающихся к объектам ADO с импортированием библиотеки типов.

Таблица 4-4. Свойства объекта Error, доступные сценариям VBScript

Свойство

Описание

Description

Текст описания ошибки

Number

Численный код ошибки типа long

Source

Объект, вызвавший появление ошибки

SQLState

Информация об ошибке от источника данных SQL

NativeError

Информация об ошибке от источника данных SQL

HelpFile

Файл электронной справочной системы Microsoft Windows Help, который содержит объяснение ошибки (иногда отсутствует)

HelpContext

Идентификатор раздела упомянутой выше электронной справочной системы с описанием ошибки (иногда отсутствует)

Как видите, на сегодняшний день VBScript способен предоставить более обширную информацию об ошибках, чем JScript. Однако для решения большинства практических задач вполне достаточно средств обработки ошибок, предусмотренных в JScript.

Рис. 4-4. Набор Errors

Набор Errors создается в рамках объекта Connection и имеет, таким образом, отношение к конкретному соединению с базой данных. Обработка ошибок заключается в том, что приложение в цикле перебирает все элементы Error набора Errors, выделяя из них код ошибки и текст сообщения об ошибке.

Способ обработки ошибок в серверных сценариях ASP сильно зависит от языка, на котором этот сценарий составлен. В литературе описано множество примеров обработки ошибок в сценариях VBScript и очень мало — в сценариях JScript. Мы попытаемся восполнить этот недостаток.

Как вы знаете, существует два принципиально разных подхода к обработке ошибок. Первый предполагает проверку кодов завершения при выполнении тех или иных операций, а второй основан на использовании исключений (exception).

Исключения доступны практически во всех современных системах программирования. Например, такие операторы, как try и catch встроены в C++ и Java. До недавних времен язык сценариев JScript не позволял обрабатывать исключения, однако, начиная c JScript версии 5.0, ситуация изменилась.

Рассмотрим следующий фрагмент кода, в котором выполняется обработка ошибок при использовании интерфейсов ADO:

try
{
  connect = Server.CreateObject("ADODB.Connection");
  connect.ConnectionTimeout = 15;
  connect.CommandTimeout = 10;
  connect.Open("DSN=BookStore", "dbo", "");
  cmd = Server.CreateObject("ADODB.Command");
  cmd.CommandText = "AddToOrder";
  cmd.CommandType = adCmdStoredProc;
  cmd.ActiveConnection = connect;
 
  cmd.Parameters.Append(cmd.CreateParameter(
     "booksID", adVarChar, adParamInput, 50, booksID));
  cmd.Parameters.Append(cmd.CreateParameter(
     "ClientID", adVarChar, adParamInput, 50, ClientID));
  cmd.Execute();
}
catch (ex)
{
  if(ex instanceof Error)
  {
     if(connect.Errors.Count==0)
       throw ex;    
     var errDescription="", errNumber=0,serrMessage="";
     for(i=0;i<connect.Errors.Count;i++)
     {
       errDescription=connect.Errors(i).description;
       errNumber=connect.Errors(i).number;
       serrMessage += ("["+errNumber+"]" + errDescription + "<br>");
     }
     Response.Redirect(
       "error.asp?ERROR=books.asp"+"&ERRMSG="+serrMessage);
  }
  else
     throw ex;
}

Строки сценария JScript, в которых выполняется обращение к интерфейсу ADO, расположены в блоке try. Когда при вызове методов этих интерфейсов или просто в процессе выполнения сценария происходит ошибка, управление передается блоку catch. Заметим, что в сценарии JScript каждому блоку try может соответствовать только один блок catch, а не несколько, как в программах, составленных на языках C++ или Java.

В качестве параметра в блок catch передается объект, содержащий информацию о возникшей ошибке. Если ошибка произошла при вызове методов интерфейсов ADO, этот объект имеет тип Error.

Наш обработчик ошибок перехватывает только такие ошибки, передавая остальные системе интерпретации серверных сценариев ASP с помощью ключевого слова throw. Для проверки принадлежности переменной ex классу Error мы используем ключевое слово instanceof.

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

Далее в цикле перебираются все элементы набора Errors, формируя в текстовой переменной serrMessage итоговое сообщение об ошибке в формате фрагмента документа HTML. Это сообщение затем передается странице error.asp через параметр ERRMSG. Параметр ERROR мы используем для передачи имени страницы ASP, в которой произошла ошибка.

Как лучше всего реализовать обработку подобных ошибок в приложении Web?

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

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

Объект Properties

Объекты Connection, Command, Recordset и Field содержат в себе объекты Properties. Объект Properties представляет собой набор объектов Propertie, представляющих параметры объектов Connection, Command, Recordset и Field. Взаимосвязь этих объектов показана на рис. 4-5.

Рис. 4-5. Набор Properties

Объекты ADO имеют встроенные и динамические объекты Properties. Первые реализуются в рамках ADO и доступны всегда, вторые обеспечиваются провайдером данных.

В таблице 4-5 перечислены встроенные объекты Properties.

Таблица 4-5. Встроенные объекты Properties

Объект

Описание

Name

Текстовая строка, идентифицирующая свойство

Type

Целое число, определяющее тип данных свойства

Value

Переменная типа Variant, содержащая значения свойства

Attributes

Целое число типа long, идентифицирующее характеристики свойства, специфические для провайдера данных

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

Константы ADO

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

Здесь возможны два способа.

Первый — включать в каждую страницу ASP, вызывающую методы ADO, специальный файл определения констант. После установки сервера SQL Server Вы найдете этот файл в каталоге Program Files\Common Files\System\ado под именем adojavas.inc. Вот небольшой фрагмент, взятый нами из этого файла:

. . .
//---- ParameterDirectionEnum Values ----
var adParamUnknown = 0x0000;
var adParamInput = 0x0001;
var adParamOutput = 0x0002;
var adParamInputOutput = 0x0003;
var adParamReturnValue = 0x0004;

//---- CommandTypeEnum Values ----
var adCmdUnknown = 0x0008;
var adCmdText = 0x0001;
var adCmdTable = 0x0002;
var adCmdStoredProc = 0x0004;
var adCmdFile = 0x0100;
var adCmdTableDirect = 0x0200;
. . .

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

Лучшее решение — импортировать библиотеку типов ADO в файле global.asa, выполняемое с помощью тега METADATA с параметром TYPE="typelib":

<!-- METADATA TYPE="typelib"
FILE="d:\program files\common files\system\ado\msado20.tlb" -->
<SCRIPT LANGUAGE=JSCRIPT RUNAT=Server>
function Session_OnStart(){}
function Session_OnEnd(){}
function Application_OnStart(){}
function Application_OnEnd(){}
</SCRIPT>

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

Изучая содержимое каталога Program Files\Common Files\System\ado, обратите внимание на подкаталог Docs. В нем находится подробный справочник по использованию ADO (на английском языке), подготовленный в формате HTML.

В каталоге ADO Вы также найдете файл adovbs.inc с определениями констант для серверных сценариев VBScript, а также текстовые файлы с описанием особенностей текущей версии ADO и другой полезной информацией.

Проект Интернет-магазина

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

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

Создание базы данных

Прежде чем приступать к изучению страниц ASP нашего Интернет-магазина, нужно создать базу данных. Мы предполагаем, что Вы уже установили Microsoft SQL Server версии 7.0, а также пакет обновлений Service Pack 1 для SQL Server (или боле новый).

Сначала мы создадим базу данных и все необходимые таблицы, затем подготовим хранимые процедуры и установим права доступа к ним.

Подготовка таблиц

Запустите приложение SQL Server Enterprise Manager и откройте сервер базы данных. Затем выберите из меню Action строку New Database. На экране появится диалоговая панель Database Properties, показанная на рис. 4-6.

Рис. 4-6. Создание базы данных

Здесь на вкладке General в поле Name введите имя базы данных, которое мы будем использовать в нашем проекте, — BookStore. Щелкнув кнопку с многоточием, расположенную в поле Location, выберите путь для размещения файла базы данных.

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

По умолчанию при создании новой базы данных на этой вкладке отмечается переключатель Automatically grow file, поэтому по мере добавления в таблицы базы данных новых записей размер файла базы данных будет увеличиваться. Величина прироста может быть указана либо в мегабайтах, либо в процентах. Пометив переключатель Restrict filegrowth в поле Maximum file size, Вы ограничите рост файла базы данных.

Вкладка Transaction Log позволяет указать размер, прирост и ограничение для файла транзакций. О том, как правильно выбирать начальные параметры при создании базы данных, в деталях описано в документации SQL Server.

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

Таблица managers

Эта таблица содержит информацию о персонале Интернет-магазина, управляющего его работой через Интернет с помощью специального административного приложения. Назначение полей таблицы managers объясняется в таблице 4-6.

Таблица 4-6. Поля таблицы managers

Поле

Тип

Описание

ManagerID

int

Ключевое поле с атрибутом IDENTITY однозначно идентифицирует запись в таблице managers

Name

varchar(50)

Имя сотрудника, используется в качестве идентификатора при подключении к административному приложению Интернет-магазина

Password

varchar(50)

Пароль сотрудника

LastLogin

datetime

Время, когда сотрудник подключался к системе в последний раз

Rights

varchar(16)

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

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

Чтобы создать данную таблицу в базе данных BookStore, запустите приложение SQL Server Enterprise Manager (если оно еще не работает), а затем выберите из меню Tools строку SQL Server Query Analyzer. В результате будет запущено приложение SQL Server Query Analyzer, главное окно которого показано на рис. 4-7.

Рис. 4-7. Приложение SQL Server Query Analyzer

Следующий шаг очень важен.

Выберите в списке DB, расположенном в правой части инструментальной панели окна Query, базу данных BookStore, как это показано на рис. 4-7. Теперь мы будем запускать программы SQL в контексте именно этой базы данных.

Далее воспользуйтесь строкой Open в меню File для выбора файла сценария SQL с именем dbo.managers.TAB, создающего таблицу managers. Содержимое этого файла показано в листинге 4-1.

Листинг 4-1 Вы найдете в файле ch4\BookShopScripts\dbo.managers.TAB на прилагаемом к книге компакт-диске.

Загрузив сценарий SQL в окно приложения SQL Server Query Analyzer, запустите его, щелкнув клавишу F5, кнопку запуска на инструментальной панели (с изображением треугольника зеленого цвета) или выбрав строку Execute из меню Query. Если Вы не допустили ошибок при вводе сценария SQL, в нижней части окна Query появится сообщение «The command(s) completed successfully».

Таблица clients

Таблица clients содержит сведения, предоставленные посетителями Вашего магазина при регистрации, а также дополнительные данные — дату регистрации и адрес IP, с которого выполнялась регистрация. Посетитель магазина работает с этой и еще одной таблицей при помощи созданного нами приложения покупателя Интернет-магазина.

Поля таблицы clients описаны в таблице 4-7.

Таблица 4-7. Поля таблицы clients

Поле

Тип

Описание

ClientID

Int

Идентификатор записи таблицы clients (ключевое поле)

UserID

varchar(50)

Идентификатор, который должен указывать покупатель при подключении к пользовательскому приложению Интернет-магазина

Password

varchar(50)

Пароль покупателя, выбранный им при регистрации

Language

varchar(50)

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

Money

Money

Общая сумма денег, потраченная покупателем в магазине с момента совершения первой покупки по настоящее время

Status

char(1)

Состояние покупателя (активный или неактивный). Не используется

LastLogin

Datetime

Дата и время последнего посещения покупателем к Интернет-магазина

UserName

varchar(50)

Полное имя покупателя, введенное им при регистрации

Email

varchar(80)

Адрес электронной почты, указанный покупателем при регистрации

mail

varchar(80)

Почтовый адрес покупателя

spam

char(3)

Флаг рассылки рекламных сообщений (в нашем приложении заполняется, но не проверяется)

RegisterDate

Datetime

Дата и время регистрации покупателя

RegisterIP

varchar(15)

Адрес IP, с которого выполнялась регистрация покупателя

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

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

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

Поля RegisterDate и RegisterIP полезны для «разборок» с недобросовестными посетителями. Средствами утилит трассировки можно определить по адресу регистрации IP доменное имя сервера поставщика услуг Интернета, которым пользуется покупатель, а затем с его помощью вычислить и самого покупателя.

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

Для создания в базе BookStore таблицы clients воспользуйтесь приложением SQL Server Query Analyzer и файлом сценария SQL с именем dbo.clients.TAB (листинг 4-2).

Листинг 4-2 Вы найдете в файле ch4\BookShopScripts\dbo.clients.TAB на прилагаемом к книге компакт-диске.

Обратите внимания на ограничения CONSTRAINT, определенные в таблице clients. Они задают начальные значения при добавлении в таблицу clients новых записей:

CREATE TABLE [dbo].[clients] (
  [ClientID] [int] IDENTITY (1, 1) NOT NULL ,
  [UserID] [varchar] (50) NOT NULL ,
  [Password] [varchar] (50) NOT NULL ,
  [Language] [varchar] (50) NOT NULL ,
  [Money] [money] NOT NULL
     CONSTRAINT [DF_clients_Money] DEFAULT (0),
  [Status] [char] (1) NOT NULL
     CONSTRAINT [DF_clients_Status] DEFAULT ('N'),
  [LastLogin] [datetime] NOT NULL
     CONSTRAINT [DF_clients_LastLogin] DEFAULT (0),
  [LoginCount] [int] NOT NULL
     CONSTRAINT [DF_clients_LoginCount] DEFAULT (0),
  [UserName] [varchar] (50) NOT NULL ,
  [Email] [varchar] (80) NOT NULL ,
  [mail] [varchar] (80) NULL ,
  [spam] [char] (3) NOT NULL
     CONSTRAINT [DF_clients_spam] DEFAULT ('no'),
  [RegisterDate] [datetime] NULL
     CONSTRAINT [DF_clients_RegisterDate] DEFAULT (getdate()),
  [RegisterIP] [varchar] (15) NULL ,
     CONSTRAINT [PK_clients] PRIMARY KEY NONCLUSTERED
  (
     [ClientID]
  ) ON [PRIMARY]
) ON [PRIMARY]

Например, поле spam инициализируется строкой no, а в поле RegisterDate автоматически заносится текущая дата, полученная от встроенной функции getdate.

Состояние пользователя, хранящееся в поле Status, отмечается символом «N». Когда посетитель сделает первую покупку, Вы можете записать сюда другое значение, например, «А».

Мы сделали поля UserID и Email уникальными:

CREATE UNIQUE INDEX [idxUserID] ON [dbo].[clients]([UserID]) ON [PRIMARY]
GO

CREATE UNIQUE INDEX [idxEMail] ON [dbo].[clients]([Email]) ON [PRIMARY]

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

Таблица books

В таблицу books сотрудники Вашего Интернет-магазина будут заносить сведения о продаваемых книгах, используя для этого административное приложение. Покупатели также будут обращаться к этой таблице, но с помощью другого приложения, созданного специально для них. В таблице books покупатели выбирают нужные книги и кладут их в свою виртуальную электронную «корзину».

Поля таблицы books описаны в таблице 4-8.

Таблица 4-8. Поля таблицы books

Поле

Тип

Описание

booksID

Int

Идентификатор записи таблицы books (ключевое поле)

Author

Varchar(50)

Автор книги

Title

Varchar(200)

Название книги

Publisher

Varchar(50)

Название издательства, выпустившего книгу

Price

Money

Стоимость книги в условных единицах

AddDate

Datetime

Дата и время добавления информации о книге в таблицу books

Annotation

Varchar(2048)

Аннотация на книгу

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

Обратите внимание на поле Annotation, предназначенное для хранения аннотации на книгу. Его максимальная длина составляет 2 048 байт, что стало возможным только с появлением SQL Server версии 7.0. Максимально поле типа varchar может содержать 8 000 символов, что позволяет хранить в поле Annotation довольно объемистые аннотации.

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

Сценарий SQL, предназначенный для создания таблицы books, находится в файле dbo.books.TAB (листинг 4-3).

Листинг 4-3 хранится в файле ch4\BookShopScripts\dbo.books.TAB на прилагаемом к книге компакт-диске.

Ограничение CONSTRAINT на поле AddDate автоматизирует процесс записи даты добавления книги в таблицу books:

CREATE TABLE [dbo].[books] (
  [booksID] [int] IDENTITY (1, 1) NOT NULL ,
  [Author] [varchar] (50) NOT NULL ,
  [Title] [varchar] (200) NOT NULL ,
  [Publisher] [varchar] (50) NOT NULL ,
  [Price] [money] NOT NULL ,
  [AddDate] [datetime] NOT NULL
     CONSTRAINT [DF_books_AddDate] DEFAULT (getdate()),
  [Annotation] [varchar] (2048) NOT NULL ,
     CONSTRAINT [PK_books] PRIMARY KEY  NONCLUSTERED
  (
     [booksID]
  ) ON [PRIMARY]
) ON [PRIMARY]

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

Таблица orders

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

Таблица 4-9. Поля таблицы orders

Поле

Тип

Описание

ordersID

Int

Идентификатор записи таблицы orders (ключевое поле)

booksID

Int

Идентификатор книги, отобранной для покупки

ClientID

Int

Идентификатор покупателя, выбравшего данную книгу

AddDate

Datetime

Дата и время отбора книги

BookPrice

money

Стоимость отобранной книги в условных единицах

Поля booksID и ClientID представляют собой внешние ключи к таблицам books и clients соответственно. Используя эти поля, мы можем определить, какую книгу отобрал тот или иной покупатель.

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

Сценарий SQL, создающий таблицу orders, представлен в листинге 4-4.

Листинг 4-4 Вы найдете в файле ch4\BookShopScripts\dbo.orders.TAB на прилагаемом к книге компакт-диске.

Обратите внимание на ограничение CONSTRAINT поля AddDate:

CREATE TABLE [dbo].[orders] (
  [ordersID] [int] IDENTITY (1, 1) NOT NULL ,
  [booksID] [int] NOT NULL ,
  [ClientID] [int] NOT NULL ,
  [AddDate] [datetime] NOT NULL
     CONSTRAINT [DF_orders_AddDate] DEFAULT (getdate()),
  [BookPrice] [money] NOT NULL ,
     CONSTRAINT [PK_orders] PRIMARY KEY NONCLUSTERED
  (
     [ordersID]
  ) ON [PRIMARY]
) ON [PRIMARY]

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

Подготовка хранимых процедур

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

Для примера мы привели в листинге 4-5 исходный текст хранимой процедуры ClientLogin, предназначенной для подключения к Интернет-магазину зарегистрированных покупателей.

Листинг 4-5 Вы найдете в файле ch4\BookShopScripts\ dbo.ClientLogin.PRC на прилагаемом к книге компакт-диске.

В качестве входных параметров мы передаем этой процедуре идентификатор пользователя @User и пароль пользователя @Pass:

CREATE PROCEDURE ClientLogin @User varchar(50), @Pass varchar(50), @Rights varchar(16) output AS

SELECT @Rights=UserID FROM clients WHERE UserID=@User AND Password=@Pass
UPDATE clients SET LastLogin=GETDATE() WHERE UserID=@User

Результат аутентификации записывается процедурой ClientLogin в выходной параметр @Rights. Попутно наша процедура обновляет поле LastLogin таблицы clients, записывая в нее время и дату подключения, полученные от встроенной функции GETDATE.

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

После этого не забудьте выделить пользователям Интернета права на их выполнение. Для этого запустите приложение SQL Server Enterprise Manager, откройте базу данных BookStore и папку с хранимыми процедурами Stored Procedures. Выберите процедуру и щелчком правой клавиши мыши откройте страницу Stored Procedure Properties, показанную на рис. 4-8.

Рис. 4-8. Страница Stored Procedure Properties

Здесь Вы можете редактировать текст процедуры. Для сохранения изменений щелкните кнопку OK. Что же касается прав на выполнение процедуры, то их нужно добавить кнопкой Permissions. Открыв панель Object Properties, отметьте на вкладке Permissions переключатели в столбце EXEC в строках тех пользователей, которым Вы хотите предоставить доступ на выполнение процедуры.

Создание источника данных

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

Откройте на компьютере, играющем роль сервера Web, папку Control Panel и дважды щелкните пиктограмму ODBC Data Sources. После того как на экране появится панель ODBC Data Sources Administrator, откройте вкладку System DSN и нажмите кнопку Add. Вы увидите страницу мастера создания источников данных с названием Create New Data Source (рис. 4-9).

Рис. 4-9. Страница Create New Data Source

В списке, расположенном на этой странице, выберите драйвер SQL Server, а затем щелкните кнопку Finish. На экране появится первая страница мастера создания источника данных для SQL Server, показанная на рис. 4-10.

Рис. 4-10. Первая страница мастера создания источника данных для SQL Server

В поле Name этой страницы введите имя создаваемого источника данных — BookStore. В поле Description Вы также можете описать источник данных.

Далее в списке Server выберите сервер базы данных, к которому будет выполняться подключение. Если серверы Web и SQL Server установлены на одном компьютере (как у нас), нужно выбрать в этом списке строку (local). Если же сервер SQL Server работает на другом компьютере, в этом списке укажите нужный сервер базы данных.

Заполнив поля на первой странице, щелкните кнопку Next.

На следующей странице мастера (рис. 4-11), Вы должны выбрать способ аутентификации при подключении к SQL Server.

Рис. 4-11. Выбор способа аутентификации

При использовании аутентификации Windows NT Вы можете оставить переключатели в том положении, как они показаны на рис. 4-11. Щелчок кнопки Client Configuration позволит Вам выбрать сетевую библиотеку и указать параметры подключения для выбранной библиотеки (рис. 4-12).

Рис. 4-12. Выбор сетевой библиотеки и настройка ее параметров

При создании источника данных для Интернет-магазина Вы можете оставить эти параметры в исходном состоянии.

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

Рис. 4-13. Выбор базы данных по умолчанию

Остальные элементы управления, расположенные в этой панели, можно не менять.

Снова щелкните кнопку Next. В панели, показанной на рис. 4-14, укажите нужный язык системных сообщений и пометьте переключатель Use regional ettings when outputting currency, numbers, dates and times.

Рис. 4-14. Последняя панель мастера создания источника данных

Для завершения работы мастера щелкните в этой панели кнопку Finish. На экране появится описание конфигурации созданного источника данных (рис. 4-15).

Рис. 4-15. Описание конфигурации созданного источника данных

Теперь Вам нужно проверить работоспособность источника, например, при помощи кнопки Test Data Source. Щелкните ее, и если Вы все сделали правильно, на экране появится сообщение об успешном завершении теста (рис. 4-16).

Рис. 4-16. Сообщение об успешной проверке источника данных

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

Подготовка виртуальных каталогов сервера Web

Наш Интернет-магазин состоит из двух приложений ASP. Первое предназначено для сотрудников магазина, и мы назовем его административным. Второе приложение будут запускать посетители Вашего магазина. Мы назовем его приложением покупателя.

Виртуальный каталог приложения покупателя BookShopClient

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

Этот виртуальный каталог, так же как и виртуальный каталог административного приложения, создается средствами управляющей консоли Microsoft Management Console. Укажите имя каталога как BookShopClient, проследив за тем, чтобы для него были разрешены чтение и исполнение сценариев. Для этого в панели BookShopClient Properties откройте вкладку Virtual Directory и пометьте переключатели Read и Script (если они были выключены).

Разрешите также настройку серверных и клиентских сценариев на вкладке App Debugging и с помощью переключателей Enable ASP server-side script debugging и Enable ASP client-side script debugging. Для того чтобы эти изменения вступили в силу, закройте панель и перезагрузите компьютер.

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

Виртуальный каталог административного приложения BookShop

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

Создайте виртуальный каталог с именем BookShop, воспользовавшись для этого приложением Microsoft Management Console. Для ограничения доступа отредактируйте свойства виртуального каталога BookShop, открыв в панели BookShop Properties вкладку Directory Security (рис. 4-17).

Рис. 4-17. Настройка доступа к виртуальному каталогу

Прежде всего, на этой вкладке Вы можете запретить доступ анонимных пользователей Интернета к данному каталогу. Для этого щелкните кнопку Edit в поле Anonymous Assess and Authentication Control. На экране появится панель Authentication Methods (рис. 4-18).

Рис. 4-18. Отмена доступа анонимных пользователей

Снимите в этой панели отметку с переключателя Allow Anonymous Access, оставив переключатель Windows NT Challenge/Response в отмеченном состоянии.

Следующий шаг — создание списка адресов IP для администраторов.

Щелкните кнопку Edit в поле IP Address and Domain Name Restrictions на вкладке Directory Security (рис. 4-17). Вы увидите панель редактирования списка адресов IP, показанную на рис. 4-19.

Рис. 4-19. Панель IP Address and Domain Name Restrictions

Отметьте в этой панели переключатель Denied Access. В результате к данному виртуальному каталогу будет запрещен доступ со всех адресов IP, за исключением перечисленных в списке. Список редактируется с помощью кнопок Add (добавление нового адреса), Remove (удаление адреса) и Edit (редактирование адреса).

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

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

Приложение покупателя

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

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

Когда посетитель попадает на, в окне его браузера появляется форма, показанная на рис. 4-20.

Рис. 4-20. Первая страница приложения покупателя

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

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

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

Рис. 4-21. Сообщение новым покупателям о необходимости зарегистрироваться

Щелкнув ссылку Вам нужно зарегистрироваться, посетитель попадает на входную страницу, показанную на рис. 4-20.

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

Форма регистрации новых посетителей показана на рис. 4-22.

Рис. 4-22. Форма регистрации нового покупателя

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

Как мы уже говорили ранеераньше при описании полей таблицы clients, содержащей сведения о посетителях, поля идентификатора пользователя и электронного адреса сделаны уникальными. Если при регистрации посетитель укажет идентификатор или адрес электронной почты, введенный ранее другим посетителем и хранящиеся в базе данных, он получит сообщение об ошибке с предложением повторит регистрацию. Форма с этим сообщением показана на рис. 4-23.

Рис. 4-23. Сообщение об ошибке при регистрации

В случае успешной регистрации посетитель увидит на экране другое сообщение —приглашение для входа (рис. 4-24).

Рис. 4-24. Приглашение для входа

Щелкнув ссылку Входите!, посетитель вновь попадет на начальную страницу, показанную на рис. 4-20. Теперь ему нужно ввести свой идентификатор и пароль, а затем щелкнуть кнопку Вход.

Выполнив описанные выше действия, покупатель наконец попадает на главную страницу магазина, состоящую из трех фреймов (рис. 4-25). Левый представляет собой меню команд, состоящее всего из двух строк — Выход и Оплатить покупки. Верхний фрейм содержит список книг, имеющихся в продаже, а в нижнем фрейме отображается содержимое корзины покупателя. При первом посещении магазина корзина пуста.

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

Рис. 4-25. Страница, которая отображается при первом посещении магазина

Как видно из рис. 4-25, в правом верхнем фрейме отображается информация из таблицы books, описанной нами ранее. Эта информация вводится и редактируется сотрудниками Вашего магазина при помощи административного приложения, которое мы рассмотрим позже.

Справа от названия книги под ее стоимостью находится ссылка, щелкнув которую покупатель добавит книгу в свою корзину. Список отобранных книг появится в правом нижнем фрейме (рис. 4-26).

Рис. 4-26. Список книг, которые покупатель отобрал в свою корзину

Ссылка Удалить из корзины позволит посетителю отказаться от покупки той или иной книги.

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

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

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

Файл global.asa

В корне виртуального каталога BookShopClient, созданного для страниц клиентского приложения, необходимо разместить файл global.asa (листинг 4-6).

Листинг 4-6 Вы найдете в файле ch4\BookShopClient\global.asa на прилагаемом к книге компакт-диске.

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

<!-- METADATA TYPE="typelib"
FILE="d:\program files\common files\system\ado\msado20.tlb" -->

Проверяя работу нашего приложения, отредактируйте путь, указанный в параметре FILE тега METADATA, таким образом, чтобы он указывал на файл msado20.tlb Вашего сервера Web.

Страницы входа и начальной регистрации

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

Вход зарегистрированных посетителей

Форма входа зарегистрированных покупателей, показанная на рис. 4-20, определена в файле default.asp (листинг 4-6).

Листинг 4-6 Вы найдете в файле ch4\BookShopClient\default.asp на прилагаемом к книге компакт-диске.

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

Изучая листинг 4-6, Вы можете заметить, что в нем находятся две формы. Первая форма предназначена для входа зарегистрированных покупателей:

<form ACTION="enter.asp" METHOD="post" TARGET="_top">
  <h2>Вход для покупателей</h2>
  <table BORDER="0" CELLPADDING="5" CELLSPACING="0">
     <tr>
       <td>Имя </td>
       <td><input SIZE="10" TYPE="EDIT" NAME="USR"> </td>
     </tr>
     <tr>
       <td>Пароль </td>
       <td><nobr>
       <input SIZE="10" TYPE="password" NAME="PWD">
     <input TYPE="submit" VALUE="Вход"></nobr></td>
     </tr>
  </table>
</form>  

Она ссылается на страницу enter.asp. Вторая форма содержит кнопку с надписью Регистрация, щелкнув которую, посетитель попадает на страницу регистрации regdataenter.asp:

<form ACTION="regdataenter.asp" METHOD="post" TARGET="_top">
  <h2>Пожалуйста, зарегистрируйтесь</h2>
  <table BORDER="0" CELLPADDING="5" CELLSPACING="0">
     <tr><td></td><td>
       <input type="submit" value="Регистрация" name="B1"></td>
     </tr>
  </table>
</form>   </td></tr>

Вначале мы рассмотрим исходный текст страницы enter.asp (листинг 4-7).

Листинг 4-7 Вы найдете в файле ch4\BookShopClient\enter.asp на прилагаемом к книге компакт-диске.

При загрузке этой странице передается из формы содержимое полей с именами USR и PWD (идентификатор посетителя и его пароль соответственно).

Серверный сценарий, расположенный на странице enter.asp, получает идентификатор пользователя и пароль, обращаясь к объекту Rerquest, а затем сохраняет соответствующие строки в переменных с именами sUser и sPassword:

var sUser=Request("USR")(1);
var sPassword=Request("PWD")(1);

Далее сценарий создает объект Connection и открывает соединение с источником данных, вызывая для этого метод Open:

var connect;
connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout = 10;
connect.Open("DSN=BookStore", "dbo", "");

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

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

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

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "ClientLogin";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;

Хранимая процедура ClientLogin имеет два входных параметра (идентификатор и пароль посетителя) и один выходной (результат аутентификации).

Входные параметры определяются в объекте Command следующим образом:

cmd.Parameters.Append(cmd.CreateParameter(
  "User", adVarChar, adParamInput, 50, sUser));
cmd.Parameters.Append(cmd.CreateParameter(
  "Pass", adVarChar, adParamInput, 50, sPassword));

Имена параметров указаны как User и Pass. Это текстовые строки, поэтому тип параметров мы указываем как adVarChar. Константа adParamInput определяет, что параметры User и Pass являются входными. Длина текстовых строк, передаваемых через данные параметры, не превышает 50 байт. И наконец, значения параметров берутся из переменных sUser и sPassword.

Выходной параметр определяется аналогичным образом, но с применением константы adParamOutput:

var ParamOut = cmd.CreateParameter(
  "Rights", adVarChar, adParamOutput, 50, " ");

После формирования параметров они добавляются к команде методом Append, после чего команда выполняется при помощи метода Execute:

cmd.Parameters.Append(ParamOut);
cmd.Execute();

Если в процессе создания соединения с источником данных, формирования команды и ее параметров, а также при выполнении команды не возникло никаких ошибок, соединение закрывается методом Close:

connect.Close();

Строго говоря, можно не закрывать соединение подобным образом, так как после того как пользователь обратится к другой странице, оно будет автоматически закрыто. Однако это может произойти не сразу (по умолчанию через 20 минут), поэтому для освобождения ресурсов сервера SQL Server мы закрываем соединение явным образом.

Далее мы проверяем значение, полученное от хранимой процедуры ClientLogin через параметр ParamOut.

Если аутентификация пользователя прошла успешно, в этом параметре будет находиться значение, отличное от null. В этом случае мы сохраняем в переменных сеанса результат аутентификации, признак успешной идентификации и идентификатор пользователя, а затем передаем управление странице main.asp:

if(ParamOut.value != null)
{
  Session("Rights")=ParamOut.value;
  Session("Ok")="Ok";
  Session("UserID")=sUser;
  Response.Redirect("main.asp");
}
else
{
  Session("Ok")="";
  Session("UserID")="";
  Session("Rights")="";

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

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

И наконец, переменная сеанса UserID идентифицирует покупателя и используется, например, для формирования его персональной корзины.

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

Обработка ошибок

В том случае, когда при обращении к базе данных возникли ошибки, мы их обрабатываем в блоке catch. Использованная при этом методика была описана ранее, поэтому здесь мы только скажем, что сообщение об ошибке передается странице error.asp:

Response.Redirect(
  "error.asp?ERROR=enter.asp"+"&ERRMSG="+serrMessage);

При этом через параметр ERROR мы передаем имя файла страницы, в которой произошла ошибка, а в переменной ERRMSG — сообщение об ошибке, подготовленное в блоке catch.

Исходный текст страницы error.asp показан в листинге 4-8.

Листинг 4-8 Вы найдете в файле ch4\BookShopClient\error.asp на прилагаемом к книге компакт-диске.

Здесь мы информируем посетителя о том, что произошла ошибка в приложении, для исправления которой необходимо вмешательство администратора сервера Web. Далее отображается название страницы, в которой произошла ошибка, и текст сообщения об ошибке:

<h2>Ошибка в приложении</h2>
<p>Обратитесь к администратору сервера Web. </p>
<p>[<%=Request("ERROR")%>]<br><%=Request("ERRMSG")%>

На рис. 4-27 показано сообщение, появляющееся в том случае, если Вы пытаетесь создать соединение с несуществующим источником данных.

Рис. 4-27. Сообщение об ошибке при соединении с источником данных

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

[enter.asp]
[-2147217911][Microsoft][ODBC SQL Server Driver][SQL Server]EXECUTE permission denied on object 'ClientLogin', database 'BookStore', owner 'dbo'.

Кстати, если мы вовсе опустим обработку ошибок на странице enter.asp, то ошибка все равно будет обработана, но уже системой интерпретации страниц ASP. При этом в случае, например, отсутствия прав на выполнение хранимой процедуры в окне браузера посетителя появится сообщение, показанное на рис. 4-28.

Рис. 4-28. Сообщение об ошибке, отображаемое интерпретатором ASP

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

Хранимая процедура ClientLogin

Теперь мы расскажем Вам о том, как устроена хранимая процедура ClientLogin, которая вызывается из сценария страницы enter.asp и выполняет аутентификацию пользователей приложения посетителей магазина.

Исходный текст хранимой процедуры ClientLogin приведен в листинге 4-9.

Листинг 4-9 Вы найдете в файле ch4\BookShopScripts\dbo.ClientLogin.PRC на прилагаемом к книге компакт-диске.

Процедура ClientLogin имеет два входных параметра @User и @Pass и один выходной @Rights:

CREATE PROCEDURE ClientLogin @User varchar(50), @Pass varchar(50), @Rights varchar(16) output AS

SELECT @Rights=UserID FROM clients WHERE UserID=@User AND Password=@Pass
UPDATE clients SET LastLogin=GETDATE() WHERE UserID=@User

С помощью оператора SELECT процедура выбирает идентификатор пользователя из столбца UserID таблицы clients так, чтобы идентификатор, записанный в этом столбце, совпадал с содержимым параметра @User. Кроме того, проверяется совпадение пароля пользователя (хранящегося в столбце Password) и содержимого переменной @Pass.

Если в результате выполнения такого запроса в таблице clients найдена подходящая запись, считается, что аутентификация прошла успешно. С помощью оператора UPDATE процедура ClientLogin обновляет поле LastLogin найденного пользователя, записывая в него дату и время подключения, полученные от функции GETDATE.

Определение фреймов главной страницы

После успешной аутентификации страница enter.asp загружает в окно браузера посетителя страницу определения фреймов приложения покупателя с именем main.asp. Исходный текст этой страницы, подготовленной при помощи Microsoft FrontPage, Вы найдете в листинге 4-10.

Листинг 4-10 хранится в файле ch4\BookShopClient\main.asp на прилагаемом к книге компакт-диске.

С помощью тегов <FRAMESET> и <FRAME> мы определили три фрейма с именами left, rtop и rbottom.

<frameset cols="150,*">
  <frame name="left" scrolling="no" noresize target="rtop" src="toc.asp">
  <frameset rows="57%,*">
     <frame name="rtop" target="rbottom" src="booklist.asp">
     <frame name="rbottom" src="order.asp?FIRST=1">
  </frameset>
  <noframes>
  <body>
  <p>Для посещения нашего сервера нужен браузер,
  способный работать с фреймами.</p>
  </body>
  </noframes>
</frameset>

Фрейм left загружается страницей toc.asp, содержащей команды приложения покупателя — Выход и Оплатить покупки. Фрейм rtop используется для отображения списка книг, имеющихся в продаже, и загружается страницей booklist.asp. И наконец, фрейм rbottom используется для показа содержимого корзины с книгами, отобранными посетителем для покупки. Он загружается страницей order.asp с параметром FIRST, равным единице (о назначении этого параметра Вы узнаете позже при изучении исходного текста страницы order.asp).

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

Чтобы исключить возможность прямой загрузки страницы main.asp в обход страницы аутентификации покупателей, мы включили в исходный текст этой и других страниц приложения покупателя файлы header.asp и footer.asp — при помощи оператора #include, доступного в страницах ASP:

<%@ LANGUAGE = "JScript" %>
<!-- #include file="header.asp" -->
<html>
<head>
<title>Книжный магазин</title>
. . .
</html>
<!-- #include file="footer.asp" -->

Исходные тексты этих файлов приведены в предыдущей главе (листинги 3-19 и 3-20), поэтому мы не будем здесь их повторять. Напомним только, что при загрузке любой страницы, в начало которой вставлен файл header.asp, выполняется проверка содержимого переменной сеанса с именем Ok. Если аутентификация пользователя прошла успешно, сценарий файла header.asp вставляет в документ теги <HTML> и <BODY>, а при неудаче — тег <META>, принудительно направляющий браузер на страницу аутентификации default.asp.

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

Страница меню команд

В левом фрейме страницы приложения покупателя расположено меню команд, состоящее из двух ссылок — Выход и Оплатить покупки. Исходный текст данного фрейма Вы найдете в листинге 4-11.

Листинг 4-11 хранится в файле ch4\BookShopClient\toc.asp на прилагаемом к книге компакт-диске.

Ссылка Выход отправляет покупателя на страницу default.asp, предлагающую ввести идентификатор и пароль зарегистрированного покупателя или выполнить регистрацию нового покупателя.

<a href="../BookShopClient/default.asp"
  target="_top">Выход</a><br>

Ссылка Оплатить покупки используется для загрузки страницы makeorder.asp, на которой реализуется процесс оплаты — ее мы рассмотрим позже в этой главе.

<a href="../BookShopClient/makeorder.asp"
  target="_top">Оплатить покупки</a></p>

Страница просмотра списка книг

На странице просмотра списка книг, расположенной в файле booklist.asp (листинг 4-12) посетитель может отобрать понравившиеся ему книги. Эта страница формируется из таблицы books, содержимое которой готовится сотрудниками Интернет-магазина с помощью административного приложения.

Листинг 4-12 хранится в файле ch4\BookShopClient\booklist.asp на прилагаемом к книге компакт-диске.

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

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

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

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

Набор записей создается явным образом при помощи метода CreateObject объекта Server:

rs = Server.CreateObject("ADODB.Recordset")

Далее мы его открываем, указывая методу Open объекта Recordset имя выполняемой хранимой процедуры ListBooks, соединение с источником данных connect и другие параметры:

rs.Open("ListBooks", connect,
  adOpenForwardOnly, adLockReadOnly, adCmdStoredProc);

Константа adOpenForwardOnly, передаваемая методу Open в качестве третьего параметра, определяет для открываемого набора записей тип курсора, предназначенного для просмотра записей только в одном (прямом) направлении.

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

И наконец, константа adCmdStoredProc, передаваемая методу Open через последний параметр, указывает, что первый параметр метода задает имя хранимой процедуры, подлежащей выполнению.

После получения набора записей мы проверяем свойство EOF. Если оно хранит значение True, это означает что полученный набор записей пуст. В этом случае вместо списка книг в верхнем правом фрейме появляется сообщение «Нет записей»:

if(rs.EOF)
{%>
<TR><TD COLSPAN=4 ALIGN="CENTER">[Нет записей]</TD></TR>
<%}
else {…}

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

В цикле мы последовательно просматриваем строки набора записей, перемещаясь по ним при помощи метода MoveNext, определенного в объекте Recordset. Условием завершения цикла является равенство свойства rs.EOF значению True:

<h2>Сегодня в продаже</h2>
<TABLE BORDER=1>
<%
while (!rs.EOF)
{
%>
<tr><td><b><%=rs.Fields("Author")%>. <%=rs.Fields("Title")%></b>
<br><i><%=rs.Fields("Publisher")%></i></td>
<td><%=rs.Fields("Price")%> у.е.<br>
<a href="order.asp?ID=<%=rs.Fields("booksID")%>&FIRST=0"
target="rbottom">Положить в корзину</A>
</td><tr>
<tr><td colspan=2>Аннотация:<br>
<small><%=rs.Fields("Annotation")%></small></td><tr>
<%
  rs.MoveNext();
}
rs.Close();
}
connect.Close();
}
catch (ex)
{
. . .
}
%>
</TABLE>

Для ссылки на поля набора записей мы используем имена соответствующих столбцов, из которых формируются строки набора. Содержимое полей набора вставляется в ячейки формируемой таблицы с помощью конструкций вида «<%=rs.Fields("Author")%>».

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

<a href="order.asp?ID=<%=rs.Fields("booksID")%>&FIRST=0"
  target="rbottom">Положить в корзину</A>

Когда пользователь щелкнет ссылку, начнет загружаться страница order.asp. При этом ей будут переданы два параметра — идентификатор книги ID и флаг FIRST.

Параметр ID содержит идентификатор, по которому можно найти книгу в таблице books нашей базы данных. Без него мы бы не знали, какую именно книгу нужно добавить в корзину. Что же касается флага FIRST, то его значение, равное 0, свидетельствует, что страница order.asp загружается для добавления новой книги в список отобранных книг, а не для простого просмотра этого списка.

Когда обработка набора записей закончена, мы должны закрыть этот набор методом Close объекта Recordset:

rs.Close();

Вслед за этим надо закрыть и соединение с источником данных, вызывая метод Close объекта Connection:

connect.Close();

Исходный текст хранимой процедуры ListBooks, запускаемой на выполнение серверным сценарием страницы booklist.asp, представлен в листинге 4-13.

Листинг 4-13 Вы найдете в файле ch4\BookShopScripts\dbo.ListBooks.PRC на прилагаемом к книге компакт-диске.

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

CREATE PROCEDURE ListBooks  AS
SELECT booksID,Author, Title, Publisher, Price, AddDate,Annotation
FROM books

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

Добавление книги в корзину

Для того чтобы добавить книгу в свою корзину, покупатель щелкает ссылку Положить в корзину, расположенную в строке и названием этой книги. Об этой ссылке мы рассказывали в предыдущем разделе: она вызывает страницу order.asp с параметрами ID и FIRST.

На самом деле страница order.asp выполняет двойную функцию. Когда мы вызываем ее с параметром FIRST, равным 1, она просто отображает содержимое корзины покупателя. Если же значение этого параметра равно 0, перед таким отображением происходит добавление выбранной книги в корзину.

В следующем разделе мы рассмотрим выполнение и той, и другой операции.

Страница просмотра содержимого корзины

Особенностью страницы просмотра содержимого корзины является то, что она может выполнять одно или два обращения к базе данных в зависимости от значения параметра FIRST. Если этот параметр равен 0, сценарий вначале запускает хранимую процедуру AddToOrder, добавляющую выбранную книгу к корзине, а затем процедуру ListOrders, предназначенную для просмотра содержимого корзины.

Рассмотрим исходный текст страницы order.asp, представленный в листинге 4-14.

Листинг 4-14 Вы найдете в файле ch4\BookShopClient\order.asp на прилагаемом к книге компакт-диске.

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

Сразу после того, как страница получает управление, сценарий получает идентификатор пользователя и сохраняет его в локальной переменной с именем ClientID.

var ClientID=Session("UserID");

Этот идентификатор понадобится для отбора тех записей в таблице orders, которые имеют отношение к данному покупателю.

Далее сценарий анализирует содержимое переменной сеанса с именем FIRST. Если оно не равно 1, то считается, что страница вызвана для добавления новой книги в корзину покупателя.

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

Затем мы создаем команду cmd как объект Command и готовим параметры для запуска хранимой процедуры AddToOrder. Этой процедуре нужно передать два входных параметра — идентификатор добавляемой книги booksID и идентификатор покупателя ClientID, добавившего книгу. После этого команда запускается на выполнение методом Execute:

if(Request("FIRST")(1) != 1)
{
  var booksID=Request("ID")(1);
  var connect;
  var cmd;
  connect = Server.CreateObject("ADODB.Connection");
  connect.ConnectionTimeout = 15;
  connect.CommandTimeout = 10;
  connect.Open("DSN=BookStore", "dbo", "");
 
  cmd = Server.CreateObject("ADODB.Command");
  cmd.CommandText = "AddToOrder";
  cmd.CommandType = adCmdStoredProc;
  cmd.ActiveConnection = connect;
 
  cmd.Parameters.Append(cmd.CreateParameter(
     "booksID", adVarChar, adParamInput, 50, booksID));
 
  cmd.Parameters.Append(cmd.CreateParameter(
     "ClientID", adVarChar, adParamInput, 50, ClientID));

  cmd.Execute();
}

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

if(Request("FIRST")(1) == 1)
{
  connect = Server.CreateObject("ADODB.Connection");
  connect.ConnectionTimeout = 15;
  connect.CommandTimeout = 10;
  connect.Open("DSN=BookStore", "dbo", "");
}

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

Запуск процедуры выполняется с помощью метода Execute:

cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "ListOrders";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;
 
cmd.Parameters.Append(cmd.CreateParameter(
  "ClientID", adVarChar, adParamInput, 50, ClientID));

var rs;
rs=cmd.Execute();

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

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

<h2>Вы отобрали для покупки</h2>
<TABLE BORDER=1>
<%
while (!rs.EOF)
{
%>
<tr><td><b><%=rs.Fields("Author")%>. <%=rs.Fields("Title")%></b>
<br><i><%=rs.Fields("Publisher")%></i></td>
<td><%=rs.Fields("Price")%> у.е.<br>
<a href="delorder.asp?ID=<%=rs.Fields("booksID")%>" target="rbottom">Удалить из корзины</A>
</td><tr>
<%
  rs.MoveNext();
}
%>
</TABLE>

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

<a href="delorder.asp?ID=<%=rs.Fields("booksID")%>"   target="rbottom">Удалить из корзины</A>

Здесь выполняется вызов страницы delorder.asp, причем в качестве параметра эта страница получает идентификатор удаляемой книги. Что же касается идентификатора покупателя, то эту информацию страница delorder.asp «добывает» самостоятельно из переменной сеанса с именем UserID.

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

rs.Close();
connect.Close();

На странице order.asp вызываются две хранимые процедуры с именами AddToOrder и ListOrders. Расскажем о том, как они работают.

Исходный текст процедуры AddToOrder, предназначенной для добавления новых книг в корзину покупателя, показан в листинге 4-15.

Листинг 4-15 Вы найдете в файле ch4\BookShopScripts\dbo.AddToOrder.PRC на прилагаемом к книге компакт-диске.

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

Сначала процедура AddToOrder должна определить идентификатор записи в таблице clients, соответствующий идентификатору покупателя, передаваемому в процедуру через входной параметр @ClientID.

Это задачу решает первый оператор SELECT. Найденный идентификатор сохраняется в переменной @nClientID типа INT и затем используется при добавлении записей в таблицу orders:

CREATE PROCEDURE AddToOrder @booksID varchar(50),
  @ClientID varchar(50) AS

DECLARE @nClientID INT
SELECT @nClientID=clients.ClientID FROM clients
WHERE UserID=@ClientID

Второй оператор SELECT определяет стоимость выбранной книги по ее идентификатору и записывает в переменную @bookPrice:

DECLARE @bookPrice MONEY
SELECT @bookPrice=Price FROM books WHERE booksID=@booksID

Оператор INSERT добавляет в таблицу orders новую запись, заполняя поля идентификатора выбранной книги booksID, идентификатора покупателя ClientID, поместившего книгу в корзину, и стоимость книги bookPrice:

INSERT orders (booksID, ClientID, bookPrice)
VALUES(@booksID, @nClientID,@bookPrice)

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

Исходный текст хранимой процедуры ListOrders Вы найдете в листинге 4-16.

Листинг 4-16 хранится в Файле ch4\BookShopScripts\dbo.ListOrders.PRC на прилагаемом к книге компакт-диске.

Она получает единственный входной параметр @ClientID — идентификатор покупателя, содержимое корзины которого необходимо извлечь из таблицы orders.

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

Вначале процедура ListOrders получает идентификатор записи покупателя, обращаясь к содержимому переменной @ClientID. Этот идентификатор сохраняется в переменной @nClientID:

CREATE PROCEDURE ListOrders @ClientID varchar(50) AS
DECLARE @nClientID INT
SELECT @nClientID=clients.ClientID FROM clients
WHERE UserID=@ClientID

Затем выполняется оператор SELECT, возвращающий информацию о книгах, заинтересовавших данного посетителя. Она получается с помощью объединения таблиц orders и books оператором JOIN:

SELECT books.booksID, books.Author, books.Title, books.Publisher, books.Price FROM orders
JOIN books ON orders.booksID=books.booksID
WHERE orders.ClientID=@nClientID

Удаление книги из корзины

Если посетитель передумал покупать книгу, отобранную в корзину, он может удалить ее оттуда, щелкнув ссылку Удалить из корзины, расположенную рядом с книгой. При этом будет загружена страница delorder.asp (листинг 4-17).

Листинг 4-17 Вы найдете в файле ch4\BookShopClient\delorder.asp на прилагаемом к книге компакт-диске.

Первое действие, выполняемое сценарием сразу после загрузки страницы, — получение из переменной сеанса UserID идентификатора посетителя и сохранение этого идентификатора в переменной ClientID:

var ClientID=Session("UserID");

Далее сценарий запускает хранимую процедуру DelOrder, передавая ей два параметра:

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");
var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "DelOrder";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;
 
cmd.Parameters.Append(cmd.CreateParameter("ClientID", adVarChar, adParamInput, 50, ClientID));
cmd.Parameters.Append(cmd.CreateParameter("bookID", adVarChar, adParamInput, 50, Request("ID")(1)));
cmd.Execute();

Первый параметр с именем ClientID хранит только что упомянутый идентификатор посетителя, удаляющего книгу из своей корзины. Второй параметр называется bookID и передается в страницу delorder.asp из страницы order.asp. Он содержит идентификатор удаляемой книги.

После удаления книги из корзины нужно обновить правый нижний фрейм, отображающий содержимое корзины покупателя. Мы это делаем с помощью метода Redirect объекта Response:

Response.Redirect("order.asp?FIRST=1");

Обратите внимание, что при загрузке страницы order.asp мы указываем параметр FIRST, равный единице. Как Вы помните, это значение свидетельствует, что нужно показать текущее содержимое корзины, не выполняя над ней никаких операций.

Исходный текст хранимой процедуры DelOrder представлен в листинге 4-18.

Листинг 4-18 находится в файле ch4\BookShopScripts\dbo.DelOrder.PRC на прилагаемом к книге компакт-диске.

Процедура DelOrder выполняется в два этапа.

На первом из таблицы clients извлекается идентификатор записи посетителя по его идентификатору, полученному процедурой через параметр @ClientID.

CREATE PROCEDURE DelOrder @ClientID varchar(50), @bookID varchar(50) AS
DECLARE @nClientID INT
SELECT @nClientID=clients.ClientID FROM clients
WHERE UserID=@ClientID

На втором этапе из таблицы orders удаляются все записи о книгах с идентификатором @bookID, добавленные данным посетителем.

DELETE orders
WHERE booksID=@bookID AND ClientID=@nClientID

Административное приложение

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

Прежде чем приступить к работе с административным приложением, Вам надо ввести идентификатор администратора и пароль на странице аутентификации (рис. 4-29).

Рис. 4-29. Вход в административное приложение

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

После успешного ввода пароля на экране появляется главная страница административного приложения, показанная на рис. 4-30.

Рис. 4-30. Главная страница административного приложения

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

Первое, что должен сделать администратор, войдя в систему, — определить список управляющего персонала магазина. Для этого он должен щелкнуть команду Управляющий персонал. После этого в правом фрейме появится список персонала (рис. 4-31).

Рис. 4-31. Список менеджеров

После первого запуска в этом списке будет только одна запись — первого подключившегося администратора. Ссылка Новый сотрудник позволит Вам добавить другие записи. Когда Вы щелкнете ее мышью, в правом фрейме появится форма регистрации нового сотрудника (рис. 4-32).

Рис. 4-32. Добавление записи о новом менеджере

Здесь задаются идентификатор и пароль сотрудника, а также его права (или, если хотите, функциональные обязанности).

Для добавления сотрудника в базу данных щелкните кнопку Создать.

Ссылка Изменить, расположенная напротив каждой записи в списке персонала, позволяет отредактировать идентификатор, пароль и права сотрудника. Соответствующая форма показана на рис. 4-33.

Рис. 4-33. Редактирование учетной записи сотрудника

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

Ссылки Удалить в списке сотрудников предназначены для удаления учетных записей.

Если щелкнуть в левом фрейме ссылку Книги, на экране появится список книг, имеющихся в продаже (рис. 4-34).

Рис. 4-34. Просмотр списка книг, имеющихся в продаже

Первоначально этот список пуст. Чтобы добавить в него название книги, щелкните ссылку Новая книга. На экране появится форма, предназначенная для добавления новой книги (рис. 4-35).

Рис. 4-35. Добавление в список названия книги

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

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

Рис. 4-36. Редактирование сведений о книге

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

И наконец, чтобы удалить книгу из базы данных, достаточно щелкнуть ссылку Удалить напротив названия книги в списке просмотра, показанном на рис. 4-34.

Команда Покупатели, расположенная в левом фрейме страницы административного приложения, открывает доступ для просмотра таблицы посетителей client и редактирования некоторых ее полей. Если щелкнуть эту ссылку, на экране появится форма, позволяющая задать критерии поиска клиентов (рис. 4-37).

Рис. 4-37. Форма для поиска зарегистрированных покупателей

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

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

Так как поиск записей посетителей выполняется с применением оператора сравнения LIKE, в полях Идентификатор покупателя и Адрес E-Mail можно использовать символы шаблонов, стандартные для языка Transact-SQL, — %[]^_.

На рис. 4-38 показан полный список зарегистрированных покупателей, полученный с помощью только что описанной формы поиска.

Рис. 4-38. Полный список зарегистрированных покупателей

Командой Изменить Вы можете отредактировать некоторые параметры регистрации выбранного пользователя (рис. 4-39).

Рис. 4-39. Редактирование параметров регистрации покупателя

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

Команда Корзина позволяет сотруднику магазина просмотреть содержимое корзины выбранного пользователя (рис. 4-40).

Рис. 4-40. Просмотр содержимого корзины покупателя

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

Вернемся к списку покупателей.

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

Если щелкнуть ссылку Удалить, на экране появится временная модальная диалоговая панель с предупреждением (рис. 4-41).

Рис. 4-41. Диалоговая панель с предупреждением

Вы можете в этой панели отказаться от удаления, щелкнув ссылку Отменить, или подтвердить свои намерения при помощи ссылки Удалить.

Если корзина удаляемого покупателя пуста, операция будет выполнена успешно. Это подтвердит сообщение, показанное на рис. 4-42.

Рис. 4-42. Сообщение об успешном удалении учетной записи покупателя

Если же покупатель отобрал книги в корзину, наше административное приложение откажется удалять его учетную запись, о чем и сообщит администратору (рис. 4-43).

Рис. 4-43. Сообщение о невозможности удалении учетной записи активного покупателя

Файл global.asa

В корне виртуального каталога BookShop, созданного нами для страниц административного приложения, необходимо поместить файл global.asa. Вы можете использовать здесь тот же самый файл, что и для приложения покупателя. Его исходный текст мы уже приводили в листинге 4-6.

Страницы входа

Рассмотрим исходные тексты страниц, предназначенные для входа сотрудников Вашего Интернет-магазина в административное приложение.

Страница default.asp (листинг 4-19) должна загружаться в окно браузера по умолчанию, когда сотрудник магазина пытается просмотреть содержимое виртуального каталога административного приложения.

Листинг 4-19 хранится в файле ch4\BookShop\default.asp на прилагаемом к книге компакт-диске.

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

<FORM ACTION="enter.asp" METHOD="post" TARGET="_top">
  <H1>Добро пожаловать!</H1>
  <TABLE BORDER=0 CELLPADDING=5 CELLSPACING=0>
     <TR><TD>Имя</TD><TD>
       <INPUT SIZE=10 TYPE="EDIT" NAME="USR">
     </TD></TR>
     <TR><TD>Пароль</TD><TD><nobr>
       <INPUT SIZE=10 TYPE="password" NAME="PWD">
       <INPUT TYPE="submit" VALUE="Вход"></nobr>
     </TD></TR>
  </TABLE>
</FORM></TD></TR>

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

После щелчка кнопки Вход управление передается странице Enter.asp, выполняющей аутентификацию (листинг 4-20).

Листинг 4-20 Вы найдете в файле ch4\BookShop\Enter.asp на прилагаемом к книге компакт-диске.

В целом она напоминает аналогичную страницу аутентификации приложения покупателя, однако есть и отличия.

Серверный сценарий получает идентификатор пользователя и пароль из полей формы с именами USR и PWD, записывая их в переменные sUser и sPassword соответственно:

var sUser=Request("USR")(1);
var sPassword=Request("PWD")(1);

Вначале серверный сценарий устанавливает соединение с источником данных:

var connect;
connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout = 10;
connect.Open("DSN=BookStore", "dbo", "");

Далее для выполнения аутентификации вызывается хранимая процедура ManagerLogin, работающая с таблицей managers:

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "ManagerLogin";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;
 
cmd.Parameters.Append(cmd.CreateParameter(
  "User", adVarChar, adParamInput, 50, sUser));
 
cmd.Parameters.Append(cmd.CreateParameter(
  "Pass", adVarChar, adParamInput, 50, sPassword));
 
var ParamOut = cmd.CreateParameter(
  "Rights", adVarChar, adParamOutput, 16, " ");
 
cmd.Parameters.Append(ParamOut);
cmd.Execute();

После выполнения этой процедуры результат аутентификации сохраняется в переменной ParamOut, а права сотрудника, пытающегося подключится к приложению, — в переменной сеанса Rights. Далее выполняется загрузка главной страницы административного приложения main.asp:

if(ParamOut.value!=" ")
{
  Session("Rights")=ParamOut.value;
  Session("Ok")="Ok";
  Session("UserID")=sUser;
  Response.Redirect("main.asp");
}

Если аутентификация закончилась с ошибкой, в окне приложения появляется сообщение Доступ запрещен, оформленное в виде ссылки на страницу аутентификации default.asp:

else
{
  Session("Ok")="";
  Session("UserID")="";
  Session("Rights")="";
%>
<HTML>
<BODY>
<CENTER><H1><a href=default.asp>Доступ запрещен</a></H1></CENTER>
</BODY>
</HTML>
<%}%>

Главная страница

Главная страница административного приложения содержит определение двух фреймов (листинг 4-21).

Листинг 4-21 Вы найдете в файле ch4\BookShop\main.asp на прилагаемом к книге компакт-диске.

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

<!-- #include file="header.asp" -->
. . .
<frameset cols="201,*">
  <frame name="contents" target="main" src="toc.asp">
  <frame name="main" src="hello.asp">
  <noframes>
  <body>
  <p>Для просмотра этой страницы нужен браузер, способный работать с фреймами.</p>
  </body>
  </noframes>
</frameset>
. . .
<!-- #include file="footer.asp" -->

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

Страница меню команд

Исходный текст страницы команд toc.asp представлен в листинге 4-22.

Листинг 4-22 Вы найдете в файле ch4\BookShop\toc.asp на прилагаемом к книге компакт-диске.

Для загрузки страниц ASP, выполняющих команды, мы применили здесь функцию клиентского сценария to, составленную на языке JavaScript:

<SCRIPT LANGUAGE=JavaScript>
function to(url)
{
  parent.main.document.location.href=
     url+"?"+Math.random().toString();
}
</SCRIPT>

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

Для просмотра и редактирования списка управляющего персонала страница toc.asp загружает страницу managers.asp. Редактирование списка книг, имеющихся в продаже, выполняет страница books.asp, а работу со списком покупателей — страница CustomerSearch.asp:

<a href="javascript:to('managers.asp')">Управляющий персонал</a><br>
<a href="javascript:to('books.asp')">Книги</a><br>
<a href="javascript:to('CustomerSearch.asp')">Покупатели</a>

Страница с сообщением о подключении

При первой загрузке главной страницы административного приложения в окно правого фрейма загружается страница hello.asp, показанная в листинге 4-23.

Листинг 4-23 хранится в файле ch4\BookShop\hello.asp на прилагаемом к книге компакт-диске.

В ней содержится приветственное сообщение, включающее идентификатор сотрудника, работающего с приложением, и его права. Эта информация, определяемая при аутентификации, извлекается из переменных сеанса с именами UserID и Rights соответственно:

<%
  var sRights="";
  if(Session("Rights") == "Administrator")
     sRights="Администратор";
  else if(Session("Rights") == "Sales_manager")
     sRights="Менеджер по продажам";
  else if(Session("Rights") == "sh_manager")
     sRights="Заведующий складом";
  else
     sRights=Session("Rights");
%>
<h2>Добро пожаловать, уважаемый <%=Session("UserID")%>!</h2>
<p>Вы подключились к системе как <b><%=sRights%></b>.

Страницы управления персоналом

Страницы управления персоналом, расположенные в файле managers.asp, позволяют администратору магазина управлять учетными записями сотрудников. Эта страница используется совместно с тремя другими — newmgr.asp, delmanager.asp и edtmanager.asp, которые отвечают соответственно за создание, удаление и редактирование учетных записей.

Просмотр списка сотрудников

Просмотр списка сотрудников выполняется на странице managers.asp. Исходный текст этой страницы Вы найдете в листинге 4-24.

Листинг 4-24 хранится в файле ch4\BookShop\managers.asp на прилагаемом к книге компакт-диске.

В начале своей работы сценарий, расположенный на странице managers.asp, запускает хранимую процедуру ListManagers:

var connect;
var rs;
connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

rs = Server.CreateObject("ADODB.Recordset")
rs.Open("ListManagers", connect,
     adOpenForwardOnly, adLockReadOnly, adCmdStoredProc);

Эта процедура не имеет никаких параметров, а в результате своей работы возвращает содержимое полей Name, Rights и LastLogin всех записей файла managers. Эти поля сохраняются в наборе записей rs.

Далее сценарий проверяет наличие записей и, если файл managers не пуст, создает на их основе таблицу. Для этого в сценарии организован цикл:

<TABLE BORDER=1>
<TR><TH>Имя</TH><TH>Права</TH>
<TH>Время последнего подключения</TH><TH>Команды</TH></TR>
<%
if(rs.EOF)
{
%>
<TR><TD COLSPAN=4 ALIGN="CENTER">[Нет записей]</TD></TR>
<%
}
else
{
  var sID, sDate="";
  while (!rs.EOF)
  {
     sID=rs.Fields("Name").value;
     if(rs.Fields("LastLogin").value != null)
     {
       sDate=rs.Fields("LastLogin").value;
     }
 
     var sRights="";
     if(rs.Fields("Rights").value == "Administrator")
       sRights="Администратор";
     else if(rs.Fields("Rights").value == "Sales_manager")
       sRights="Менеджер по продажам";
     else if(rs.Fields("Rights").value == "sh_manager")
       sRights="Заведующий складом";
     else
       sRights=rs.Fields("Rights").value;
%>
<TR>
<TD><b><%=rs.Fields("Name").value%></b></TD>
<TD><%=sRights%></TD>
<TD>&nbsp;<%=sDate%></TD>
<TD>
<a href="edtmanager.asp?ID=<%=sID%>" target="main">Изменить</A>&nbsp;
<a href="delmanager.asp?ID=<%=sID%>" target="main">Удалить</A>
</TD>
</TR>
<%
     rs.MoveNext();
  }
  rs.Close();
  connect.Close();
}

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

Изменение учетной записи сотрудника выполняется на странице edtmanager.asp, а удаление — на странице delmanager.asp. И та, и другая страница получают в качестве параметра идентификатор пользователя ID, извлеченный из поля Name текущей строки обрабатываемого набора записей rs.

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

<TR><TD COLSPAN=6 ALIGN="CENTER"><a href="newmgr.asp" target="main">[Новый сотрудник]</a></TD></TR>

Исходный текст хранимой процедуры ListManagers, с помощью которой сценарий managers.asp получает список сотрудников, представлен в листинге 4-25.

Листинг 4-25 Вы найдете в файле ch4\BookShopScripts\dbo.ListManagers.PRC на прилагаемом к книге компакт-диске.

Эта процедура получает содержимое полей Name, Rights и LastLogin таблицы managers при помощи оператора SELECT и не имеет никаких интересных особенностей:

CREATE PROCEDURE ListManagers  AS
SELECT Name, Rights, LastLogin
FROM managers

Создание новой записи

Для создания новой учетной записи сотрудника магазина вызывается страница newmgr.asp, исходный текст которой приведен в листинге 4-26.

Листинг 4-26 Вы найдете в файле ch4\BookShop\newmgr.asp на прилагаемом к книге компакт-диске.

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

<form ACTION="createmgr.asp" METHOD="post">
  <h1>Новый администратор</h1>
  <table BORDER="0" CELLPADDING="5" CELLSPACING="0">
     <tr><td>Имя</td><td>
       <input SIZE="16" TYPE="EDIT" NAME="USR">
     </td></tr><tr>
     <td>Пароль </td> <td>
       <input SIZE="16" TYPE="EDIT" NAME="PWD"> </td>
     </tr><tr> <td>Права</td> <td>
       <select NAME="RG" size="1">
       <option selected value="Administrator">Все права</option>
       <option value="Sales_manager">Менеджер по продажам</option>
       <option value="sh_manager">Заведующий складом</option>
       </select></td>   </tr>
     <tr><td>&nbsp; </td><td>
       <input TYPE="submit" VALUE="Создать"> </td>
     </tr>
  </table>
</form>  

Вы можете указать различные права (они зависят от структуры фирмы), отредактировав набор тегов <OPTION> в списке <SELECTION>.

Щелчок кнопки Создать отправляет данные, подготовленные в полях формы, на страницу createmgr.asp (листинг 4-27). Расположенный там серверный сценарий добавляет параметры нового сотрудника в таблицу managers базы данных магазина.

Листинг 4-27 хранится в файле ch4\BookShop\createmgr.asp на прилагаемом к книге компакт-диске.

Добавление новых сотрудников разрешено только администраторам, поэтому в начале своей работы сценарий страницы createmgr.asp проверяет права пользователя, записанные в переменную сеанса с именем Rights при аутентификации. Если это администратор, сценарий продолжает свою работу, а если нет, — выводит сообщение о том, что прав недостаточно:

if(Session("Rights")=="Administrator")
{
  connect = Server.CreateObject("ADODB.Connection");
  connect.ConnectionTimeout = 15;
  connect.CommandTimeout =  10;
  connect.Open("DSN=BookStore", "dbo", "");

  var cmd = Server.CreateObject("ADODB.Command");
  cmd.CommandText = "NewMgr";
  cmd.CommandType = adCmdStoredProc;
  cmd.ActiveConnection = connect;
 
  cmd.Parameters.Append(cmd.CreateParameter(
     "User", adVarChar, adParamInput, 50, Request("USR")(1)));
    
  cmd.Parameters.Append(cmd.CreateParameter(
     "Pwd", adVarChar, adParamInput, 50, Request("PWD")(1)));
      
  cmd.Parameters.Append(cmd.CreateParameter(
     "Rights", adVarChar, adParamInput, 16, Request("RG")(1)));
      
  cmd.Execute();
  connect.Close();
}
else
{
  Response.Redirect("error.asp?ERROR=Недостаточные права");
}

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

По завершении своей работы сценарий страницы createmgr.asp снова загружает в правый фрейм главной страницы административного приложения документ managers.asp:

Response.Redirect("managers.asp");

Теперь в нем появится новая только что добавленная запись.

Исходный текст хранимой процедуры NewMgr Вы найдете в листинге 4-28.

Листинг 4-28 находится в файле ch4\BookShopScripts\dbo.NewMgr.PRC на прилагаемом к книге компакт-диске.

Она просто вставляет значения, полученные через параметры @User, @Pass и @Rights в соответствующие поля таблицы managers:

CREATE PROCEDURE NewMgr @User varchar(50), @Pass varchar(50),
  @Rights varchar(16) AS
INSERT managers (Name, Password, Rights, LastLogin)
VALUES(@User, @Pass, @Rights, NULL)

Что же касается поля LastLogin, хранящего дату последнего подключения сотрудника к системе, то в нее записывается значение NULL. Мы это делаем потому, что только что зарегистрированный сотрудник еще ни разу не входил в систему.

Удаление учетной записи сотрудника

Эта операция выполняется на странице delmanager.asp (листинг 4-29).

Листинг 4-29 Вы найдете в файле ch4\ BookShop\delmanager.asp на прилагаемом к книге компакт-диске.

После проверки прав текущего пользователя серверный сценарий, расположенный на этой странице, получает со страницы managers.asp идентификатор удаляемой записи через параметр ID и запускает хранимую процедуру DelMgr:

var rs, connect;
if(Session("Rights")=="Administrator")
{
  try
  {
     connect = Server.CreateObject("ADODB.Connection");
     connect.ConnectionTimeout = 15;
     connect.CommandTimeout =  10;
     connect.Open("DSN=BookStore", "dbo", "");

     var cmd = Server.CreateObject("ADODB.Command");
     cmd.CommandText = "DelMgr";
     cmd.CommandType = adCmdStoredProc;
     cmd.ActiveConnection = connect;
 
     cmd.Parameters.Append(cmd.CreateParameter(
       "User", adVarChar, adParamInput, 50, Request("ID")(1)));

     cmd.Execute();
     connect.Close();
  }
  catch (ex)
  {
     . . .
  }
  Response.Redirect("managers.asp");
}
else
{
  Response.Redirect("error.asp?ERROR=0&ERRMSG=Недостаточные права");
}

Процедуре DelMgr (листинг 4-30) этот идентификатор передается через параметр с именем User.

Листинг 4-30 Вы найдете в файле ch4\BookShopScripts\dbo.DelMgr.PRC на прилагаемом к книге компакт-диске.

В случае успешного завершения в правый фрейм главного окна административного приложения вновь загружается страница managers.asp.

Хранимая процедура DelMgr удаляет из файла managers запись сотрудника, идентификатор которого равен идентификатору, полученному процедурой через параметр @User:

CREATE PROCEDURE DelMgr @User varchar(50) AS
DELETE managers WHERE Name=@User

Редактирование записи сотрудника

Редактирование записи сотрудника выполняется на странице edtmanager.asp (листинг 4-31).

Листинг 4-31 хранится в файле ch4\ BookShop\edtmanager.asp на прилагаемом к книге компакт-диске.

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

Если с правами все в порядке, начинает выполняться хранимая процедура GetMgr, извлекающая текущие параметры учетной записи выбранного сотрудника. Она имеет один входной параметр User (идентификатор сотрудника) и два выходных — Pwd (пароль) и Rights (права сотрудника):

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "GetMgr";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;

var Usr=cmd.CreateParameter(
  "User", adVarChar, adParamInput, 50, Request("ID")(1));

var Pwd=cmd.CreateParameter(
  "Pwd", adVarChar, adParamOutput, 50,  " ");

var Rights=cmd.CreateParameter(
  "Rights", adVarChar, adParamOutput, 16, " ");
 
cmd.Parameters.Append(Usr);
cmd.Parameters.Append(Pwd);
cmd.Parameters.Append(Rights);
cmd.Execute();
connect.Close();

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

<FORM ACTION="updatemgr.asp" METHOD="POST">
<H2>Редактирование записи пользователя <%=Usr.Value%></H2>
<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=0>
<TR><TD>Имя</TD><TD>
  <INPUT SIZE=16 TYPE="HIDDEN" NAME="ID" VALUE="<%=Usr.Value%>">
  <INPUT SIZE=16 TYPE="EDIT" NAME="USR" VALUE="<%=Usr.Value%>">
</TD></TR><TR><TD>Пароль</TD><TD>
  <INPUT SIZE=16 TYPE="EDIT" NAME="PWD" VALUE="<%=Pwd.Value%>">
</TD></TR>
<tr><td>Права </td>
<%
var sAdm="", sSMan="", sStH="";
if(Rights.Value == "Administrator")
  sAdm="selected";
else if(Rights.Value == "Sales_manager")
  sSMan="selected";
else if(Rights.Value == "sh_manager")
  sStH="selected";
%>
<td>
<select NAME="RG" size="1">
  <option <%=sAdm%> value="Administrator">Все права</option>
  <option <%=sSMan%> value="Sales_manager">Менеджер по продажам</option>
  <option <%=sStH%>  value="sh_manager">Заведующий складом</option>
</select> </td></tr>
<TR><TD>&nbsp;</TD><TD>
<INPUT TYPE="SUBMIT" VALUE="Сохранить изменения"></TD></TR>
</TABLE></FORM>

После щелчка кнопки Сохранить изменения управление передается странице сценария updatemgr.asp (листинг 4-32), выполняющей обновление записи в файле managers.

Запуская страницу updatemgr.asp, наш сценарий передает ей параметры ID (идентификатор записи сотрудника), USR (идентификатор сотрудника), PWD (пароль) и RG (права сотрудника), причем параметр ID передается через скрытое поле формы.

Листинг 4-32 Вы найдете в файле ch4\ BookShop\updatemgr.asp на прилагаемом к книге компакт-диске.

Задача сценария, расположенного на странице updatemgr.asp, заключается в запуске хранимой процедуры SetMgr, выполняющей обновление файла managres. Данные для обновления она получает через свои параметры @User, @NewUser, @Pass и @Rights:

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");
    
var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "SetMgr";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;

var Usr=cmd.CreateParameter(
  "User", adVarChar, adParamInput, 50, Request("ID")(1));
      
var NewUsr=cmd.CreateParameter(
  "NewUser", adVarChar, adParamInput, 50, Request("USR")(1));
      
var Pwd=cmd.CreateParameter(
  "Pwd", adVarChar, adParamInput, 50, Request("PWD")(1));
      
var Rights=cmd.CreateParameter(
  "Rights", adVarChar, adParamInput, 16, Request("RG")(1));
      
cmd.Parameters.Append(Usr);
cmd.Parameters.Append(NewUsr);
cmd.Parameters.Append(Pwd);
cmd.Parameters.Append(Rights);
cmd.Execute();
connect.Close();

Исходный текст процедуры SetMgr находится в листинге 4-33.

Листинг 4-33 хранится в файле ch4\BookShopScripts\dbo.SetMgr.PRC на прилагаемом к книге компакт-диске.

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

CREATE PROCEDURE SetMgr @User varchar(50), @NewUser varchar(50) , @Pass varchar(50) , @Rights varchar(16) AS
UPDATE managers SET Name=@NewUser, Rights=@Rights, Password=@Pass FROM managers WHERE Name=@User

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

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

Сотрудника Вашего магазина должны иметь возможность просматривать и редактировать список книг, имеющихся в продаже, а также описания этих книг. Эти задачи решаются с помощью серверных сценариев books.asp, newbook.asp, createbook.asp, delbook.asp, editbook.asp и updatebook.asp.

Просмотр списка книг

Для просмотра списка книг используется страница books.asp, исходный текст которой приведен в листинге 4-34.

Листинг 4-34 Вы найдете в файле ch4\ BookShop\books.asp на прилагаемом к книге компакт-диске.

Для получения списка всех книг сценарий вызывает хранимую процедуру ListBooks:

var connect;
var rs;
connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

rs = Server.CreateObject("ADODB.Recordset")
rs.Open("ListBooks", connect,
  adOpenForwardOnly, adLockReadOnly, adCmdStoredProc);

Если список пуст, вместо таблицы со списком книг отображается строка «Нет записей». В противном случае сценарий формирует таблицу с описанием книг, обрабатывая в цикле набор записей rs, полученный в результате работы процедуры ListBooks:

if(rs.EOF)
{
%>
<TR><TD COLSPAN=4 ALIGN="CENTER">[Нет записей]</TD></TR>
<%
}
else
{
%>
<HTML>
<BODY>
<h2>Список книг</h2>
<TABLE BORDER=1>
<tr><th>Книга</th><th>Команда</th></tr>
<%
  var i=0, sID, sDate="";
  while (!rs.EOF)
  {
%>
<tr><td><b><%=rs.Fields("Author")%>
<br><%=rs.Fields("Title")%></b><br><i><%=rs.Fields("Publisher")%></i>
<br>Цена: <%=rs.Fields("Price")%> у.е.
<br>Дата обновления: <%=rs.Fields("AddDate")%></td>
<td>
<a href="editbook.asp?ID=<%=rs.Fields("booksID")%>" target="main">Изменить</A>&nbsp;
<a href="delbook.asp?ID=<%=rs.Fields("booksID")%>" target="main">Удалить</A>
</td><tr>
<tr><td colspan=2>Аннотация:<br><small><%=rs.Fields("Annotation")%></small></td><tr>
<%
     rs.MoveNext();
  }
  rs.Close();
}
connect.Close();

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

В нижней строке таблицы размещается ссылка Новая книга:

<a href="newbook.asp" target="main">[Новая книга]</a>

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

Что же касается хранимой процедуры ListBooks (листинг 4-35), то ее задача — выбор полей booksID, Author, Title, Publisher, Price, AddDate и Annotation из таблицы books.

Листинг 4-35 Вы найдете в файле ch4\BookShopScripts\dbo.ListBooks.PRC на прилагаемом к книге компакт-диске.

Эта задача решается с помощью простого оператора SELECT:

CREATE PROCEDURE ListBooks  AS
SELECT booksID,Author, Title, Publisher, Price, AddDate,Annotation
FROM books

Добавление новой книги

Как мы только что сказали, после щелчка ссылки Новая книга управление передается странице newbook.asp (листинг 4-36).

Листинг 4-36 хранится в файле ch4\ BookShop\newbook.asp на прилагаемом к книге компакт-диске.

Она содержит форму с полями описания книги:

<form ACTION="createbook.asp" METHOD="post">
  <h2>Новая книга</h2>
  <table BORDER="0" CELLPADDING="5" CELLSPACING="0">
     <tr><td ALIGN="left" VALIGN="top">Автор </td>
          <td ALIGN="left" VALIGN="top">
     <input type="text" name="Author" size="50"> </td>
       </tr><tr><td ALIGN="left" VALIGN="top">Название </td>
          <td ALIGN="left" VALIGN="top">
     <textarea rows="2" name="Title" cols="50"></textarea> </td>
       </tr><tr><td ALIGN="left" VALIGN="top">Издатель</td>
          <td ALIGN="left" VALIGN="top">
     <select name="Publisher" size="1">
       <option value="Русская редакция">Русская редакция</option>
       <option value="Диалог-МИФИ">Диалог-МИФИ</option>
       <option value="Бином">Бином</option>
       <option value="Microsoft Press">Microsoft Press</option>
     </select></td>   </tr><tr>
          <td ALIGN="left" VALIGN="top">Цена, у.е.</td>
          <td ALIGN="left" VALIGN="top">
     <input type="text" name="Price" size="20"></td>
       </tr><tr><td ALIGN="left" VALIGN="top">Аннотация</td>
          <td ALIGN="left" VALIGN="top">
     <textarea rows="5" name="Annotation" cols="50">
     </textarea></td></tr><tr>
          <td ALIGN="left" VALIGN="top">&nbsp; </td>
          <td ALIGN="left" VALIGN="top">
     <input TYPE="submit" VALUE="Добавить"> </td></tr>
  </table>
</form>  

После заполнения всех полей формы сотрудник магазина должен щелкнуть кнопку Добавить, тогда данные из полей формы будут переданы сценарию createbook.asp (листинг 4-37).

Листинг 4-37 хранится в файле ch4\ BookShop\createbook.asp на прилагаемом к книге компакт-диске.

Задачей этого сценария является получение указанных выше данных через параметры Author, Title, Publisher, Price и Annotation, с последующей записью в таблицу books.

Запись сведений о новой книге выполняется хранимой процедурой NewBook с применением уже знакомой Вам техники:

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "NewBook";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;
 
cmd.Parameters.Append(cmd.CreateParameter(
  "Author", adVarChar, adParamInput, 50, Request("Author")(1)));
      
cmd.Parameters.Append(cmd.CreateParameter(
  "Title", adVarChar, adParamInput, 200, Request("Title")(1)));
      
cmd.Parameters.Append(cmd.CreateParameter(
  "Publisher", adVarChar, adParamInput, 50,
   Request("Publisher")(1)));
      
cmd.Parameters.Append(cmd.CreateParameter(
  "Price", adCurrency, adParamInput, 50, Request("Price")(1)));
      
cmd.Parameters.Append(cmd.CreateParameter(
  "Annotation", adVarChar, adParamInput, 2048,
  Request("Annotation")(1)));
      
cmd.Execute();
connect.Close();

Исходный текст хранимой процедуры NewBook приведен в листинге 4-38.

Листинг 4-38 Вы найдете в файле ch4\BookShopScripts\dbo.NewBook.PRC на прилагаемом к книге компакт-диске.

Хранимая процедура NewBook имеет одну особенность — она записывает аннотацию к книге в поле Annotation, размером 2 048 байт:

CREATE PROCEDURE NewBook @Author varchar(50), @Title varchar(200), @Publisher varchar(50), @Price money, @Annotation varchar(2048) AS
INSERT books (Author, Title, Publisher, Price, Annotation) VALUES(@Author, @Title, @Publisher, @Price, @Annotation)

Такой размер поля возможен только в таблицах SQL Server версии 7.0. Что же касается версии 6.5, то для нее эта величина ограничена значением 255 байт, поэтому хранение больших объемов текста нужно выполнять по-другому (например, с использованием полей типа text).

Удаление книги

Если сотрудник магазина захочет удалить книгу из базы данных и щелкнет ссылку Удалить в списке книг, управление получит страница delbook.asp (листинг 4-39).

Листинг 4-39 вы найдете в файле ch4\ BookShop\delbook.asp на прилагаемом к книге компакт-диске.

Данная страница запускает хранимую процедуру DelBook, передавая ей в качестве единственного параметра идентификатор удаляемой книги bookID:

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");
var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "DelBook";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;
 
cmd.Parameters.Append(cmd.CreateParameter(
  "bookID", adVarChar, adParamInput, 50, Request("ID")(1)));
cmd.Execute();
connect.Close();

Этот идентификатор извлекается из параметра запуска страницы с именем ID.

Исходный текст хранимой процедуры DelBook Вы найдете в листинге 4-40.

Листинг 4-40 находится в файле ch4\BookShopScripts\dbo.DelBook.PRC на прилагаемом к книге компакт-диске.

Эта процедура удаляет из файла books записи с идентификатором, переданным ей через параметр @bookID:

CREATE PROCEDURE DelBook @bookID varchar(50) AS
DELETE books WHERE booksID=@bookID

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

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

Первый этап выполняется при помощи сценария editbook.asp (листинг 4-41).

Листинг 4-41 Вы найдете в файле ch4\ BookShop\editbook.asp на прилагаемом к книге компакт-диске.

После проверки прав пользователя сценарий запускает хранимую процедуру GetBook, определяя для нее один входной и несколько выходных параметров:

var rs, connect;
connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "GetBook";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;

var bookID=cmd.CreateParameter(
  "bookID", adVarChar, adParamInput, 50, Request("ID")(1));
      
var Author=cmd.CreateParameter(
  "Author", adVarChar, adParamOutput, 50, " ");
      
var Title=cmd.CreateParameter(
  "Title", adVarChar, adParamOutput, 200, " ");
      
var Publisher=cmd.CreateParameter(
  "Publisher", adVarChar, adParamOutput, 50, " ");
      
var Price=0;
Price=cmd.CreateParameter(
  "Price", adCurrency, adParamOutput, 8, 0);
      
var AddDate=cmd.CreateParameter(
  "AddDate", adVarChar, adParamOutput, 50, " ");
      
var Annotation=cmd.CreateParameter(
  "Annotation", adVarChar, adParamOutput, 2048, " ");
 
cmd.Parameters.Append(bookID);
cmd.Parameters.Append(Author);
cmd.Parameters.Append(Title);
cmd.Parameters.Append(Publisher);
cmd.Parameters.Append(Price);
cmd.Parameters.Append(AddDate);
cmd.Parameters.Append(Annotation);
cmd.Execute();

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

<FORM ACTION="updatebook.asp" METHOD="POST">
<H2>Редактирование сведений о книге</H2>
<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=0>
<tr>
  <td ALIGN="left" VALIGN="top"><b>Автор</b></td>
  <td ALIGN="left" VALIGN="top">
<input type="text" name="Author" size="50" value="<%=Author.Value%>"> </td></tr><tr>
  <td ALIGN="left" VALIGN="top"><b>Название</b></td>
  <td ALIGN="left" VALIGN="top">
<textarea rows="2" name="Title" cols="50"><%=Title.Value%></textarea> </td></tr><tr>
  <td ALIGN="left" VALIGN="top"><b>Издатель</b></td>
  <td ALIGN="left" VALIGN="top">
<%
var s1="", s2="", s3="", s4="";
if(Publisher.Value == "Русская Редакция")
  s1="selected";
else if(Publisher.Value == "Диалог-МИФИ")
  s2="selected";
else if(Publisher.Value == "Бином")
  s3="selected";
else if(Publisher.Value == "Microsoft Press")
  s4="selected";
%>
<select name="Publisher" size="1">
<option <%=s1%> value="Русская Редакция">Русская Редакция</option>
<option <%=s2%> value="Диалог-МИФИ">Диалог-МИФИ</option>
<option <%=s3%> value="Бином">Бином</option>
<option <%=s4%> value="Microsoft Press">Microsoft Press</option>
</select></td>
</tr><tr>
  <td ALIGN="left" VALIGN="top"><b>Цена, у.е.</b></td>
  <td ALIGN="left" VALIGN="top">
<input type="text" name="Price" size="20" value="<%=Price.Value%>">
  </td></tr><tr>
  <td ALIGN="left" VALIGN="top"><b>Аннотация</b></td>
  <td ALIGN="left" VALIGN="top">
<textarea rows="5" name="Annotation" cols="50"><%=Annotation.Value%>
</textarea></td></tr>
<TR><TD>&nbsp;</TD><TD>
<INPUT TYPE="hidden" VALUE="<%=bookID%>" name="bookID">
<INPUT TYPE="SUBMIT" VALUE="Сохранить изменения"></TD></TR>
</TABLE></FORM>

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

Сведения о книге извлекаются из базы данных при помощи хранимой процедуры GetBook (листинг 4-42).

Листинг 4-42 Вы найдете в файле ch4\BookShopScripts\dbo.GetBook.PRC на прилагаемом к книге компакт-диске.

Эта процедура принимает в качестве входного параметра идентификатор книги @bookID, возвращая информацию о ней через выходные параметры:

CREATE PROCEDURE GetBook @bookID varchar(50), @Author varchar(50) output, @Titile varchar(200) output, @Publisher varchar(50) output, @Price money output, @AddDate datetime output, @Annotation varchar(2048) output AS

SELECT @Author=Author, @Titile=Title, @Publisher=Publisher, @Price=Price, @AddDate=AddDate, @Annotation=Annotation 
FROM books
WHERE booksID=@bookID

Обновление отредактированного описания книги в таблице books выполняется серверным сценарием, расположенным на странице updatebook.asp (листинг 4-43).

Листинг 4-43 Вы найдете в файле ch4\ BookShop\updatebook.asp на прилагаемом к книге компакт-диске.

Эта страница вызывает хранимую процедуру SetBook, обновляющую поля таблицы books:

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "SetBook";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;

var bookID=cmd.CreateParameter(
  "bookID", adVarChar, adParamInput, 50, Request("bookID")(1));
      
var Author=cmd.CreateParameter(
  "Author", adVarChar, adParamInput, 50, Request("Author")(1));
      
var Title=cmd.CreateParameter(
  "Title", adVarChar, adParamInput, 200, Request("Title")(1));
      
var Publisher=cmd.CreateParameter(
  "Publisher", adVarChar, adParamInput, 50,
  Request("Publisher")(1));
      
var Price=cmd.CreateParameter(
  "Price", adVarChar, adParamInput, 50, Request("Price")(1));
      
var Annotation=cmd.CreateParameter(
  "Annotation", adVarChar, adParamInput, 2048,
   Request("Annotation")(1));
    
cmd.Parameters.Append(bookID);
cmd.Parameters.Append(Author);
cmd.Parameters.Append(Title);
cmd.Parameters.Append(Publisher);
cmd.Parameters.Append(Price);
cmd.Parameters.Append(Annotation);
cmd.Execute();
connect.Close();

Исходный текст процедуры приведен в листинге 4-44.

Листинг 4-44 Вы найдете в файле ch4\BookShopScripts\dbo.SetBook.PRC на прилагаемом к книге компакт-диске.

Процедура выполняет обновление полей таблицы books значениями, переданными ей через параметры:

CREATE PROCEDURE SetBook @bookID varchar(50), @Author varchar(50), @Title varchar(200), @Publisher varchar(50), @Price varchar(50), @Annotation varchar(2048) AS

UPDATE books SET Author=@Author, Title=@Title, Publisher=@Publisher, Price=CONVERT(money, @Price), Annotation=@Annotation
FROM books WHERE booksID=@bookID

Работа с записями покупателей

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

Форма поиска покупателей

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

Исходный текст страницы CustomerSearch.asp, предназначенной для задания параметров поиска, представлен в листинге 4-45.

Листинг 4-45 Вы найдете в файле ch4\ BookShop\CustomerSearch.asp на прилагаемом к книге компакт-диске.

Обратите внимание на то, что в этой странице мы используем одновременно и клиентские, и серверные сценарии. Для выбора посетителей по дате регистрации мы включили в исходный текст страницы файл calendar.js, описанный во второй главе. С помощью средств Dynamic HTML этот файл создает на странице два календаря, предназначенных для указания начальной и конечной даты регистрации.

Срезу после загрузки страницы, однако, в дело включается серверный сценарий. Анализируя содержимое переменной сеанса SearchUsersFirstUse, серверный сценарий выбирает для инициализации календаря одну из двух функций клиентского сценария: buildmap или buildmapEx:

<%
if(Session("SearchUsersFirstUse") == null)
{
%>
<body onload="buildmap()">
<%
}
else
{
  dDateCheck=Session("dSearchUsersDateCheck");
  dLogName=Session("dSearchUsersLogName");
  dEMail=Session("dSearchUsersEMail");
%>
<body onload="buildmapEx(<%=Session("dSearchUsersFromFY")%>,
<%=Session("dSearchUsersFromFM")%>, <%=Session("dSearchUsersFromFD")%>,
<%=Session("dSearchUsersToTY")%>,
<%=Session("dSearchUsersToTM")%>, <%=Session("dSearchUsersToTD")%>,
'<%=(dLogName == "") ? '' : dLogName%>',
'<%=(dEMail == "") ? '': dEMail%>',
'<%=dDateCheck%>')">
<%
}%>

Функция buildmap отображает на странице календарные даты, ограничивающие период времени по умолчанию (длительностью в одну неделю).

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

Переменная сеанса dSearchUsersDateCheck хранит флаг поиска по диапазону дат, а переменные dSearchUsersLogName и dSearchUsersEMail — имя и адрес электронной почты посетителя. Переменные сеанса dSearchUsersFromFY, dSearchUsersFromFM и dSearchUsersFromFD содержат компоненты начальной даты регистрации посетителя, а переменные dSearchUsersToFY, dSearchUsersToFM и dSearchUsersToFD — компоненты конечной даты регистрации посетителя.

На странице CustomerSearch.asp находится также функция клиентского сценария с именем go:

function go()
{
var sLogName=escape(document.all.SearchForm.LogName.value);
var sEMail=escape(document.all.SearchForm.CEMail.value);

var sUseDataCheckBox="";
if(document.all.UseDataCheckBox.checked)
{
  sUseDataCheckBox="yes";
}
else
{
  sUseDataCheckBox="no";
}

window.location.href="GetSearchResults.asp?DATECHECK="+
  sUseDataCheckBox+
  "&FY="+byear.toString()+"&FM="+bmonth.toString()+
  "&FD="+bday.toString()+
  "&TY="+eyear.toString()+"&TM="+emonth.toString()+
  "&TD="+eday.toString()+ "&MODE=first"+
  "&LOGNAME="+sLogName+"&EMAIL="+sEMail+
  "&FRCE="+Math.random().toString();
}

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

Обратите внимание, как функция go выполняет кодирование шаблона имени и адреса электронной почты, вызывая встроенную функцию JavaScript с именем escape:

var sLogName=escape(document.all.SearchForm.LogName.value);
var sEMail=escape(document.all.SearchForm.CEMail.value);

Такая кодировка необходима, чтобы параметры с символами кириллицы и другими «особыми» символами, такими, как символы пунктуации, правильно передавались вызываемой странице ASP.

Для загрузки в правый фрейм страницы GetSearchResults.asp, выполняющей поиск регистрационных записей в базе данных, функция go использует свойство браузера window.location.href. Обратите внимание на фиктивный параметр FRCE, предназначенный для отключения кэширования методом генерации случайного адреса URL.

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

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

Рассмотрим исходный текст страницы GetSearchResults.asp, выполняющей поиск учетных записей посетителей магазина в базе данных. Вы найдете его в листинге 4-46.

Листинг 4-46 хранится в файле ch4\ BookShop\GetSearchResults.asp на прилагаемом к книге компакт-диске.

Так же как и только что рассмотренная страница CustomerSearch.asp, страница GetSearchResults.asp использует одновременно клиентские и серверные сценарии. В частности, функция клиентского сценария trydeluser применяется при удалении ненужной учетной записи неактивного посетителя:

<SCRIPT LANGUAGE=javascript>
<!--
function trydeluser(sUserID, sUserLogin)
{
  var vParm;
  var rVal;
  vParm=new Array(2);
  vParm[0]=sUserID;
  vParm[1]=sUserLogin;
  rVal=showModalDialog("trydeleteuserdlg.asp", vParm, "dialogHeight:160px;dialogWidth:350px;status:0");
  if(rVal==false) return;
 
  // Удаление записи
  var sASP="";
  sASP="trydeleteacc.asp?ID=" + sUserID;
  rVal=showModalDialog(sASP, vParm, "dialogHeight:160px;dialogWidth:350px;status:0");
  if(rVal==false) return;
 
  // Удаление строки из таблицы
  sNode="A"+sUserID;
  var oDeletedRow=document.getElementById(sNode);
  oDeletedRow.removeNode(true);
}

//-->
</SCRIPT>

Когда страница GetSearchResults.asp получает управление в первый раз, расположенный на ней серверный сценарий записывает в переменную сеанса с именем SearchUsersFirstUse строку «used»:

Session("SearchUsersFirstUse")="used";

Далее сценарий вызывает хранимую процедуру SearchUsers, возвращающую список найденных учетных записей посетителей:

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "SearchUsers";
cmd.CommandType = 4;
cmd.ActiveConnection = connect;

var dFrom, dTo, dLogName, dName, dEMail, dDateCheck;

// После обновления записи
if(Request("MODE")(1) == "restart")
{
  dFrom=Session("dSearchUsersFrom");
  dTo=Session("dSearchUsersTo");
  dLogName=Session("dSearchUsersLogName");
  dEMail=Session("dSearchUsersEMail");
  dDateCheck=Session("dSearchUsersDateCheck");
}
 
// После страницы поиска
else
{
  dFrom=trim(Request("FY")(1), 4)+trim(Request("FM")(1), 2)
     +trim(Request("FD")(1), 2);
  Session("dSearchUsersFrom")=dFrom;
  Session("dSearchUsersFromFY")=trim(Request("FY")(1), 4);
  Session("dSearchUsersFromFM")=trim(Request("FM")(1), 2);
  Session("dSearchUsersFromFD")=trim(Request("FD")(1), 2);

  dTo=trim(Request("TY")(1), 4)+trim(Request("TM")(1),2)
     +trim(Request("TD")(1), 2);
  Session("dSearchUsersTo")=dTo;
  Session("dSearchUsersToTY")=trim(Request("TY")(1), 4);
  Session("dSearchUsersToTM")=trim(Request("TM")(1), 2);
  Session("dSearchUsersToTD")=trim(Request("TD")(1), 2);

  dDateCheck=Request("DATECHECK")(1);
  Session("dSearchUsersDateCheck")=dDateCheck;

  dLogName=Request("LOGNAME")(1);
  if(dLogName == "")
     dLogName="%";
  Session("dSearchUsersLogName")=dLogName;

  dEMail=Request("EMAIL")(1);
  if(dEMail == "")
     dEMail="%";
  Session("dSearchUsersEMail")=dEMail;
}

cmd.Parameters.Append(cmd.CreateParameter("From", 200, 1, 13,
  dFrom));
cmd.Parameters.Append(cmd.CreateParameter("To", 200, 1, 13, dTo));
cmd.Parameters.Append(cmd.CreateParameter("Login", 200, 1, 20,
  dLogName));
cmd.Parameters.Append(cmd.CreateParameter("Email", 200, 1, 80,
  dEMail));
cmd.Parameters.Append(cmd.CreateParameter("DateCheck", 200, 1, 20,
  dDateCheck));

rs = cmd.Execute();

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

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

На следующем этапе серверный сценарий страницы GetSearchResults.asp формирует документ HTML, записывая в него параметры поиска и таблицу с найденными учетными записями. При этом в локальной переменной nCounter подсчитывается общее количество найденных покупателей, а в переменной nSumm выполняется подсчет общего количества денег, полученных от них:

<h2>Покупатели</h2>
<%if(Session("dSearchUsersDateCheck") == "yes")
{%>
<p>Зарегистрировались в период от <b><%=dFrom%></b> до <b><%=dTo%></b>
<%} else { %>
<p>Зарегистрировались в любое время
<%}%>
<br>Идентификатор: <b><%=(dLogName != "") ? dLogName : "[любой]"%></b>,
EMail: <b><%=(dEMail != "") ? dEMail : "[любой]"%></b></p>
<TABLE BORDER=1>
<TR>
<TH>Идентификатор</TH><TH>Полное имя</TH><TH>Деньги</TH>
<TH>Дата регистрации</TH><TH>Адрес E-Mail</TH><TH>Команды</TH>
</TR>
<%
  var nSumm=0;
  var nCounter=0;
  if(rs.EOF)
  {
%>
<TR><TD COLSPAN=6 ALIGN="CENTER">[Список покупателей пуст]</TD></TR>
<%
}
else
{
  i=0;
  while (!rs.EOF)
  {
     nSumm += (rs.Fields("Money").value)*100;
     nCounter++;
     var sID=rs.Fields("ClientID").value;
%>
<TR ID="A<%=sID%>">
<TD><b><%=rs.Fields("UserID").value%></TD>
<TD><%=rs.Fields("UserName").value%></TD>
<TD><%=rs.Fields("Money").value%></TD>
<TD><%=rs.Fields("RegisterDate").value%></TD>
<TD><%=rs.Fields("Email").value%></TD>
<TD>
<a href="EditUser.asp?ID=<%=sID%>" target="main">Изменить</A>&nbsp;
<a href="order.asp?ID=<%=rs.Fields("UserID").value%>" target="main">Корзина</A>
<a href="javascript:trydeluser(<%=sID%>, '<%=rs.Fields("UserName").value%>')" target="main">Удалить</A>
</TD>
</TR>
<%
     rs.MoveNext();
  }
}
rs.Close();
connect.Close();
%>
<TR>
<TD COLSPAN="2"><b>Всего покупателей: <%=nCounter%></b></TD><TD><%=nSumm/100%></TD><TD COLSPAN="3">&nbsp;</TD>
</TR>
</TABLE>

Для обеспечения возможности работы с дробными значениями денежных сумм мы вначале умножаем на 100 сумму денег, полученную от каждого покупателя, а затем делим на 100 общую сумму.

В каждой строке создаваемой таблицы серверный сценарий располагает ссылки для изменения или удаления учетной записи, а также для просмотра содержимого корзины покупателя. Операции редактирования учетной записи и просмотра корзины выполняются при помощи страниц EditUser.asp и order.asp. Что же касается удаления, то для этого мы вызываем функцию клиентского сценария с именем trydeluser. В качестве параметров данной функции передаются идентификатор пользователя и его имя.

Рассмотрим исходный текст хранимой процедуры SearchUsers (листинг 4-47).

Листинг 4-47 Вы найдете в файле ch4\BookShopScripts\dbo.SearchUsers.PRC на прилагаемом к книге компакт-диске.

Она выполняет поиск учетных записей покупателей в таблице clients в соответствии с указанными параметрами.

Параметр поиска @DateCheck задает необходимость отбора учетных записей покупателей по дате их регистрации в базе данных магазина. Если этот параметр указан как no (это будет в том случае, если в форме поиска снята отметка с переключателя искать по дате регистрации:), дата регистрации не проверяется. Если же это не так, то для удовлетворения условиям поиска дата регистрации должна находится в пределах, заданных параметрами @from и @to:

CREATE PROCEDURE SearchUsers @from datetime =null,
@to datetime =null, @Login varchar(20), @Email varchar(80), @DateCheck varchar(20) AS

IF @from IS NULL
BEGIN
  SELECT @to=GETDATE()
  SELECT @from=DATEADD(dd, -7, @to)
END

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

SELECT ClientID, USerID, Password, Language, clients.Money, Status, LastLogin, LoginCount, UserName, Email, mail, spam, RegisterDate, RegisterIP
FROM clients
WHERE
  (@DateCheck='no' OR (RegisterDate>=@from AND RegisterDate<=@to))
AND
  UserID LIKE @Login
AND
  Email LIKE @Email

Удаление записи покупателя

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

Рассмотрим работу функции trydeluser (листинг 4-46). Ее основная задача — отображать предупреждающее сообщение об удалении пользователя, а также предотвращать удаление учетных записей пользователей, отобравших в свои корзины какие-либо книги (то есть учетные записи активных покупателей).

Для отображения предупреждающего сообщения об удалении учетной записи посетителя эта функция отображает на экране модельную диалоговую панель trydeleteuserdlg.asp (листинг 4-48), вызывая ее при помощи функции клиентского сценария showModalDialog. При этом функция trydeluser передает странице trydeleteuserdlg.asp массив параметров vParm с идентификатором пользователя и его именем, используемым для входа в магазин.

Листинг 4-48 Вы найдете в файле ch4\ BookShop\trydeleteuserdlg.asp на прилагаемом к книге компакт-диске.

Клиентский сценарий, находящийся на странице trydeleteuserdlg.asp, извлекает переданные ему параметры, используя имя пользователя для формирования предупреждающего сообщения об удалении пользователя:

<h3>Внимание!</h3>
<p>Учетная запись<br><b><script LANGUAGE="javascript">
<!--
document.write(window.dialogArguments[1]);
//-->
</script></b><br>будет удалена!

Если пользователь решит продолжить удаление и щелкнет ссылку Удалить, управление получит функция клиентского сценария tryDeleteAcc, определенная на странице trydeleteuserdlg.asp. При отказе от удаления с помощью ссылки Отменить будет вызвана функция клиентского сценария cancelDeleteAcc:

<a onclick="tryDeleteAcc()">
  <font color="red">Удалить</font></a>
. . .
<a onclick="cancelDeleteAcc()">
  <font color="red">Отменить</font></a>

Функция tryDeleteAcc завершает работу модельной диалоговой панели, возвращая значение true:

function tryDeleteAcc()
{
  window.returnValue=true;
  event.returnValue=false;
  window.close();
}

Функция cancelDeleteAcc сообщает об отмене удаления возвращением значения false:

function cancelDeleteAcc()
{
  window.returnValue=false;
  event.returnValue=false;
  window.close();
}

Теперь мы снова вернемся к функции trydeluser.

Если сотрудник магазина решил продолжить удаление учетной записи, эта функция создает еще одну модальную диалоговую панель, загружая в нее страницу trydeleteacc.asp:

var sASP="";
sASP="trydeleteacc.asp?ID=" + sUserID;
rVal=showModalDialog(sASP, vParm,
 "dialogHeight:160px;dialogWidth:350px;status:0");
if(rVal==false)
  return;

Страница trydeleteacc.asp пытается удалить учетную запись посетителя из базы данных. Если это ей удается, мы удаляем средствами DHTML строку с этой записью из общего списка:

sNode="A"+sUserID;
var oDeletedRow=document.getElementById(sNode);
oDeletedRow.removeNode(true);

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

Рассмотрим исходный текст страницы trydeleteacc.asp со сценарием, выполняющим попытку удаления учетной записи из базы данных (листинг 4-49).

Листинг 4-49 Вы найдете в файле ch4\BookShop\trydeleteacc.asp на прилагаемом к книге компакт-диске.

Получив управление, сценарий проверяет текущие права сотрудника, разрешая удалять учетные записи посетителей только администраторам. Далее вызывается хранимая процедура DeleteUser, выполняющая попытку удаления. Ей передается один входной параметр ID (идентификатор пользователя) и один выходной — DelOK, сообщающий о результате выполнения операции удаления (для примера мы в этом и некоторых других сценариях передаем методу Append числовые, а не символьные значения):

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

var cmd = Server.CreateObject("ADODB.Command");
var DelOk;
cmd.CommandText = "DeleteUser";
cmd.CommandType = 4;
cmd.ActiveConnection = connect;
 
cmd.Parameters.Append(cmd.CreateParameter(
  "User", 3, 1, 4, Request("ID")(1)));
    
cmd.Parameters.Append(DelOk=cmd.CreateParameter(
  "DelOk", 3, 3, 4, 0));
    
cmd.Execute();

После выполнения хранимой процедуры DeleteUser серверный сценарий анализирует содержимое выходного параметра DelOK и формирует соответствующее сообщение. Кроме того, в зависимости от результата операции создается один из двух вариантов функции клиентского сценария closeDialogOK, закрывающей окно модальной диалоговой панели. Если удаление прошло успешно, эта функция возвращает значение true, а если нет — false:

DelOk = DelOk.Value;
if(DelOk == 0)
{
%>
<script LANGUAGE="javascript">
<!--
function closeDialogOK()
{
  window.returnValue=true;
  event.returnValue=false;
  window.close();
}
//-->
</script>
<table width="100%" height="100%">
  <tr height="50%">
     <td width="100%" align="middle" valign="bottom" colspan="2">
     <p>Учетная запись <br><b><script LANGUAGE="javascript">
<!--
document.write(window.dialogArguments[1]);
//-->
</script></b><br>успешно удалена</td>
  </tr>
  <tr height="50%">
     <td width="45%" align="middle" valign="top">
     <a onclick="closeDialogOK()">
     <font color="red">OK</font></a></td>
  </tr>
</table>
<%
}
else
{
%>
<script LANGUAGE="javascript">
<!--
function closeDialogOK()
{
  window.returnValue=false;
  event.returnValue=false;
  window.close();
}
//-->
</script>
<table width="100%" height="100%">
  <tr height="50%">
     <td width="100%" align="middle" valign="bottom" colspan="2">
     <p>Невозможно удалить активную учетную запись:<p><b><script LANGUAGE="javascript">
document.write(window.dialogArguments[1]);
//-->
</script></b></td>
  </tr>
  <tr height="50%">
     <td width="45%" align="middle" valign="top">
     <a onclick="closeDialogOK()">
     <font color="red">OK</font></a></td>
  </tr>
</table>
<%
}

Исходный текст хранимой процедуры DeleteUser приводится в листинге 4-50.

Листинг 4-50 Вы найдете в файле ch4\BookShopScripts\dbo.DeleteUser.PRC на прилагаемом к книге компакт-диске.

В этой процедуре мы создаем локальную переменную @Status, присваивая ей нулевое значение. Если данный посетитель имеет записи в таблице orders (то есть если он отобрал книги для покупки), мы считаем его активным и устанавливаем содержимое переменной @Status, равное единице:

CREATE PROCEDURE DeleteUser @ID int, @Ok int =0 output AS

DECLARE @Status int
SELECT @Status=0

IF EXISTS(SELECT * FROM orders WHERE ClientID=@ID)
  SELECT @Status=1

Для пассивных посетителей выполняется удаление регистрационных записей из таблицы clients:

IF @Status=0 BEGIN
  DELETE clients WHERE ClientID=@ID
END

Результат выполнения операции удаления в любом случае записывается в выходной параметр @Ok:

SELECT @Ok=@Status

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

Сотрудник магазина может просмотреть содержимое корзин покупателей при помощи страницы order.asp (листинг 4-51). Эта страница выглядит почти так же, как и страница просмотра корзины в приложении покупателя, однако, в ней не предусмотрено изменение содержимого корзины.

Листинг 4-51 Вы найдете в файле ch4\BookShop\order.asp на прилагаемом к книге компакт-диске.

Серверный сценарий, расположенный на этой странице, вызывает хранимую процедуру ListOrders — ее мы уже описывали ранее в разделах, посвященных приложению покупателя:

var ClientID=Request("ID")(1);

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout = 10;
connect.Open("DSN=BookStore", "dbo", "");
 
cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "ListOrders";
cmd.CommandType = adCmdStoredProc;
cmd.ActiveConnection = connect;
 
cmd.Parameters.Append(cmd.CreateParameter(
  "ClientID", adVarChar, adParamInput, 50, ClientID));

var rs;
rs=cmd.Execute();

Идентификатор покупателя (корзину которого мы будем просматривать) извлекается сценарием из параметра ID и затем передается хранимой процедуре с использованием локальной переменной ClientID.

Далее сценарий формирует таблицу, содержащую список книг, отобранных покупателем:

<h2>Содержимое корзины покупателя <%=ClientID%></h2>
<TABLE BORDER=1>
<%
while (!rs.EOF)
{
%>
<tr><td><b><%=rs.Fields("Author")%>. <%=rs.Fields("Title")%></b>
<br><i><%=rs.Fields("Publisher")%></i></td>
<td><%=rs.Fields("Price")%> у.е.</td><tr>
<%
  rs.MoveNext();
}
rs.Close();
connect.Close();
%>
</TABLE>

Редактирование регистрационных данных покупателя

Эта операция выполняется в два приема. Вначале загружается страница EditUser.asp, исходный текст которой представлен в листинге 4-52. Она получает сведения о регистрационной записи покупателя из базы данных магазина и отображает их в форме. Затем данные из формы попадают на страницу UpdateUser.asp, обновляющую запись в базе данных.

Листинг 4-52 Вы найдете в файле ch4\BookShop\EditUser.asp на прилагаемом к книге компакт-диске.

В начале своей работы страница EditUser.asp проверяет права текущего пользователя, разрешая редактирование регистрационных записей покупателей только администраторам. Затем выполняется запуск хранимой процедуры GetUser, извлекающей регистрационную запись из базы данных. В качестве входного параметра мы передаем этой процедуре идентификатор покупателя:

connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "GetUser";
cmd.CommandType = 4;
cmd.ActiveConnection = connect;

var ID = cmd.CreateParameter(
  "ID", adInteger, adParamInput, 4, Request("ID")(1));
cmd.Parameters.Append(ID);

var UserID = cmd.CreateParameter(
  "UserID", adVarChar, adParamOutput, 50, " ");
cmd.Parameters.Append(UserID);

var Pass = cmd.CreateParameter(
  "Pass", adVarChar, adParamOutput, 50, " ");
cmd.Parameters.Append(Pass);

var Language = cmd.CreateParameter(
  "Language", adVarChar, adParamOutput, 50, " ");
cmd.Parameters.Append(Language);

var Money= cmd.CreateParameter(
  "Money", adCurrency, adParamOutput, 8, "0");
cmd.Parameters.Append(Money);

var RegisterDate = cmd.CreateParameter(
  "RegisterDate", adDBTimeStamp, adParamOutput, 8, 0);
cmd.Parameters.Append(RegisterDate);

var RegisterIP = cmd.CreateParameter(
  "RegisterIP", adVarChar, adParamOutput, 15, " ");
cmd.Parameters.Append(RegisterIP);

var Email = cmd.CreateParameter(
  "Email", adVarChar, adParamOutput, 80, " ");
cmd.Parameters.Append(Email);

cmd.Execute();

Обратите внимание, как мы инициализируем параметры хранимой процедуры, имеющие отношение к данным различных типов.

Для целых чисел мы указываем константу adInteger, текстовые строки передаются с использованием константы adVarChar, а для передачи денежных данных применяется константа adCurrency. И наконец, отметка о времени передается с использованием константы adDBTimeStamp.

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

<FORM ACTION="UpdateUser.asp" METHOD="post">
<H2>Редактирование учетной записи посетителя "<%=UserID.Value%>"</H2>
<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=0>
<TR><TD>Идентификатор</TD><TD>
<INPUT SIZE=20 TYPE="hidden" NAME="ID" VALUE="<%=ID.Value%>">
<INPUT SIZE=20 TYPE="EDIT" NAME="USR" VALUE="<%=UserID.Value%>">
</TD></TR>
<TR><TD>Пароль</TD><TD>
<INPUT TYPE="EDIT" NAME="PWD" VALUE="<%=Pass.Value%>">
</TD></TR><TR><TD>Язык</TD><TD>"<%=Language.Value%>"</TD></TR>
<TR><TD>Деньги</TD><TD>"<%=Money.Value%>"</TD></TR>
<TR><TD>Дата регистрации</TD><TD>
<%
var sRegDate="";
sRegDate=RegisterDate.Value;
if(sRegDate==null)
{ sRegDate="не известна" }
%>
<%=sRegDate%>
</TD></TR><TR><TD>Адрес IP при регистрации</TD><TD>
<%
sIP=RegisterIP.Value;
if(sIP==null)
{sIP="не известен"}
%>
<%=sIP%></TD></TR>
<TR><TD>Адрес Email</TD><TD>
<%
var sEmail="";
sEmail=Email.Value;
if(sEmail==null)
{sEmail="не известен"}
%>
<INPUT TYPE="EDIT" NAME="EMAIL" VALUE="<%=sEmail%>">
</TD></TR><TR><TD>&nbsp;</TD><TD>
<INPUT TYPE="submit" VALUE="Обновить">
</TD></TR></TABLE></FORM></TD></TR></TABLE>

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

Исходный текст хранимой процедуры GetUser представлен в листинге 4-53.

Листинг 4-53 Вы найдете в файле ch4\BookShopScripts\dbo.GetUser.PRC на прилагаемом к книге компакт-диске.

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

CREATE PROCEDURE GetUser @ID int, @UserID varchar(50) output, @Pass varchar(50) output, @Language varchar(50) output, @Money money output,   @RegisterDate datetime output, @RegisterIP varchar(15) output, @Email varchar(80) output AS

SELECT @UserID="", @Pass="", @Language="", @Money=0, @RegisterDate="", @RegisterIP="", @Email=""

SELECT @UserID=UserID, @Pass=Password, @Language=Language, @Money=Money, @RegisterDate=RegisterDate, @RegisterIP=RegisterIP, @Email=Email
FROM clients WHERE ClientID=@ID

Исходный текст страницы UpdateUser.asp, выполняющей обновление регистрационной записи пользователя, показан в листинге 4-54.

Листинг 4-54 Вы найдете в файле ch4\BookShop\UpdateUser.asp на прилагаемом к книге компакт-диске.

<%@ LANGUAGE = "JScript" %>
<!-- #include file="header.asp" -->
<%
 
  Response.Redirect("GetSearchResults.asp?MODE=restart");
}
%>
<!-- #include file="footer.asp" -->

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

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

var rs, connect;
connect = Server.CreateObject("ADODB.Connection");
connect.ConnectionTimeout = 15;
connect.CommandTimeout =  10;
connect.Open("DSN=BookStore", "dbo", "");

var cmd = Server.CreateObject("ADODB.Command");
cmd.CommandText = "SetUserData";
cmd.CommandType = 4;
cmd.ActiveConnection = connect;

var ID=cmd.CreateParameter(
  "ID", 3, 1, 4, Request("ID")(1));
    
var Login=cmd.CreateParameter(
  "Name", 200, 1, 20,  Request("USR")(1));
    
var Pass=cmd.CreateParameter(
  "Pass", 200, 1, 20, Request("PWD")(1));
    
var Email=cmd.CreateParameter(
  "Email", 200, 1, 80, Request("EMAIL")(1));

cmd.Parameters.Append(ID);
cmd.Parameters.Append(Login);
cmd.Parameters.Append(Pass);
cmd.Parameters.Append(Email);
cmd.Execute();
connect.Close();

После обновления записи мы вновь попадаем на страницу GetSearchResults.asp, которая теперь вызывается с параметром MODE, имеющим значение restart:

Response.Redirect("GetSearchResults.asp?MODE=restart");

Исходный текст хранимой процедуры SetUserData представлен в листинге 4-55.

Листинг 4-55 Вы найдете в файле ch4\BookShopScripts\dbo.SetUserData.PRC на прилагаемом к книге компакт-диске.

Она обновляет поля таблицы clients значениями, полученными через свои параметры:

CREATE PROCEDURE SetUserData @ID int, @Name varchar(50),
  @Pass varchar(50), @Email varchar(80) AS
UPDATE clients SET UserID=@Name, Password=@Pass, Email=@Email
WHERE ClientID=@ID

Работа с ADO в приложениях C++

Объектная модель ADO создавалась для применения с различными языками и системами программирования, совместимыми с COM. В предыдущих разделах этой главы мы показали основные приемы использования интерфейсов и методов ADO в серверных сценариях ASP, написанных на языке Microsoft JScript. Вместе с тем модель ADO также доступна в серверных сценариях VB Script, в программах Java, Microsoft Visual Basic и в приложениях, подготовленных при помощи Microsoft Visual C++.

Такие языки программирования, как Microsoft JScript, VB Script и Microsoft Visual Basic в значительной степени «маскируют» тот факт, что работа с ADO выполняется средствами COM. Это позволяет разработчику составлять достаточно сложные программы, обращающиеся к базам данных, не затрудняя себя детальным изучением модели компонентных объектов COM. В случае использования C++ требуются более глубокие знания, хотя и здесь есть возможности для упрощения разработки приложений, интенсивно взаимодействующие с ADO.

Для чего Вам могут потребоваться приложения C++, обращающиеся к базам данных посредством ADO?

Если речь идет о приложениях для Интернета, то это нужно, прежде всего, для связи программных расширений сервера Web, таких, как программы CGI или ISAPI, с базами данных. Кроме того, иногда надо расширить объектную модель ASP, добавив собственные элементы ActiveX, обращающиеся к базам данных.

Разработчик приложения C++ может работать с ADO тремя различными способами:

·    непосредственно вызывая интерфейсы и методы ADO с помощью функций программного интерфейса Win32, предназначенных для работы с COM;

·    применяя средства библиотеки MFC, созданных для OLE;

·    импортируя библиотеки типов ADO с помощью оператора #import.

Первый из этих способов предполагает детальное знакомство программиста с методикой применения технологии COM. В частности, необходимо следить за использованием указателей на интерфейсы, своевременно захватывая их и освобождая при помощи методов AddRef и Release, а для создания объектов ADO приходится вызывать явным образом функцию CoCreateInstance.

Второй способ, ориентированный на применение MFC OLE, упрощает работу с ADO посредством классов-оболочек (wrapper class). Недостатки данного способа — невозможность использования перечислимых типов данных из библиотеки типов ADO, а также необходимость поставлять вместе с программой библиотеку динамической загрузки MFC DLL.

И наконец, третий способ, основанный на включении библиотеки типов ADO оператором #import, предполагает создание вспомогательных классов оболочек, а также автоматическую генерацию перечислимых типов и глобальных уникальных идентификаторов GUID объектов ADO. Этот способ, на наш взгляд, наиболее удобен, так как делает использование ADO в программах C++ примерно таким же простым, как и в сценариях JScript и VB Script.

Одно из важных преимуществ применения оператора #import заключается в использовании так называемых интеллектуальных указателей (smart pointer) класса _com_ptr_t, а также классов для работы с типами данных BSTR и VARIANT. Интеллектуальные указатели позволяют не беспокоиться о реализации и вызове методов QueryInterface, AddRef и Release, упрощая работу с указателями на интерфейсы COM.

Еще одна особенность, связанная с оператором #import — обработка ошибок при помощи исключений. Как известно, применение исключений для обработки ошибок вместо проверки кодов возврата сокращают объем листингов исходного текста приложений, упрощают разработку и отладку программ. Когда при создании объектов ADO или при выполнении методов ADO происходят ошибки, класс _com_error исключается. Этот вспомогательный класс упрощает обработку ошибок, так как сам вызывает методы интерфейса IErrorInfo.

Импортирование библиотеки типов ADO

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

<!-- METADATA TYPE="typelib"
FILE="d:\program files\common files\system\ado\msado20.tlb" -->

Что же касается программ C++, то для импортирования библиотеки типов необходимо воспользоваться оператором #import, расположив его в области определений исходного текста программы:

#import "d:\program files\common files\system\ado\Msado20.tlb"

Наиболее подходящее место для расположения этого файла — файл StdAfx.h, создаваемый автоматически системой разработки Microsoft Visual C++ и содержащий различные h-файлы. Разумеется, Вы можете включать оператор #import в файлы cpp, содержащие обращения к ADO.

Как работает оператор #import?

Когда компилятор встречает такой оператор, ссылающийся на ту или иную библиотеку типов, он генерирует для каждой библиотеки два текстовых файла с расширением имени tlh и tli. Например, при импортировании библиотеки типов ADO версии 2.0 создаются файлы с именами Msado20.tlh и Msado20.tli. Эти файлы создаются в каталоге с исходными текстами проекта Вашего приложения.

Файл Msado20.tlh содержит определения объектов и перечислимых типов ADO, а файл Msado20.tli — классы-оболочки для методов объектной модели ADO. Вы можете просмотреть их содержимое при помощи любого текстового редактора, например в окне редактирования Microsoft Visual C++.

Если Вам не нравится размещать в исходном тексте программы абсолютные ссылки на файлы библиотек типов, можно воспользоваться другим вариантом вызова оператора #import:

#import <Msado20.tlb>

При этом, однако, необходимо, чтобы полный путь к каталогу, содержащему библиотеку типов (в нашем случае это d:\program files\common files\system\ado), был прописан в переменной среды LIB, PATH или INCLUDE. Есть и другая возможность — добавить этот путь в список каталогов Visual C++ на вкладке Directories панели Options, вызвав ее на экран при помощи строки Options меню Tools (рис. 4-44).

Рис. 4-44. Добавление пути к каталогу с библиотекой типов ADO

После импортирования библиотеки типов указанным выше способом может возникнуть проблема с константой EOF, определенной как значение –1. Эта константа обычно используется при работе с потоками ввода/вывода, однако как Вы скоро убедитесь, в ADO ей уготовано и иное применение. Чтобы избежать конфликта имен, мы переименуем EOF из библиотеки типов ADO в adoEOF, как это показано ниже:

#import "d:\program files\common files\system\ado\Msado20.tlb" \
  rename ("EOF", "adoEOF")

Обращение к интерфейсам и методам ADO

Прежде чем привести полные исходные тексты приложения, написанного на C++ и обращающегося к базе данных средствами ADO, рассмотрим основные приемы обращения к интерфейсам и методам ADO, основанные на использовании оператора #import.

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

Автономное приложение, работающее с объектами ADO, должно до начала своей работы выполнить инициализацию системы COM вызовом функции CoInitialize. Перед завершением работы приложению необходимо освободить ресурсы, связанные с системой COM, при помощи функции CoUninitialize.

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

struct ComInit
{
  ComInit()
  {
     ::CoInitialize(NULL);
  }

  ~ComInit()
  {
     ::CoUninitialize();
  }
} com_init;

Установка соединения с источником данных

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

В следующем фрагменте кода мы создаем объект Connection и записываем в переменную с именем cn указатель на интерфейс объекта:

ADODB::_ConnectionPtr cn = NULL;
HRESULT hr = S_OK;
hr = cn.CreateInstance(__uuidof(ADODB::Connection));

Обратите внимание на то, как мы объявляем переменную cn. Здесь мы ссылаемся на пространство имен ADODB, определенное в результате импорта библиотеки типов ADO оператором #import. Тип _ConnectionPtr определен как указатель на интерфейс объекта Connection.

Однако простое создание указателя еще не влечет за собой образование объекта. Для того чтобы создать объект, мы вызываем метод CreateInstance, передавая ему в качестве параметра уникальный глобальный идентификатор GUID объекта ADODB::Connection. Этот идентификатор извлекается из файла Msado20.tli, определенным в Microsoft Visual C++, при помощи конструкции __uuidof, являющейся расширением C++.

Результат выполнения метода CreateInstance записывается в переменную hr типа HRESULT. Для проверки успеха завершения операции Вы должны использовать макрос SUCCEEDED, например:

if(!SUCCEEDED(hr))
  return;

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

Теперь, когда объект Connection успешно создан, можно открывать канал связи методом Open:

_bstr_t bsConnString(L"DSN=BookStore");
_bstr_t bsUserID(L"dbo");
_bstr_t bsUserPwd(L"");

cn->Open(bsConnString, bsUserID, bsUserPwd,
  ADODB::adConnectUnspecified);

Здесь мы передаем методу Open имя источника данных, имя и пароль пользователя, и дополнительный параметр, определяющий режим открытия (синхронный или асинхронный). Константа ADODB::adConnectUnspecified определяет синхронный режим, установленный по умолчанию. Асинхронный режим (задается константой adAsyncConnect) в нашей книге не рассматривается.

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

Обращаем Ваше внимание на класс _bstr_t. Он становится доступным в результате выполнения импорта библиотеки типов и помогает работать с типом данных BSTR. Тип BSTR используется в программировании элементов COM для передачи информации в виде текстовых строк Unicode. Класс _bstr_t облегчает создание таких строк и выполнение над ними основных операций.

После завершения операций необходимо закрыть соединение с источником данных, вызвав метод Close:

cn->Close();

Выполнение команды

Для того чтобы выполнить команду, пользуясь установленным соединением с источником данных, необходимо вызвать метод Execute:

_bstr_t bsCommand(L"select * from managers");
ADODB::_RecordsetPtr rs = NULL;
rs = cn->Execute(bsCommand, &vtMissing, ADODB::adCmdText);

В качестве первого параметра мы передаем методу Execute строку команды. Как Вы помните, это может быть оператор языка SQL, имя хранимой процедуры или имя таблицы базы данных. В нашем случае выполняется оператор SELECT, выбирающий все данные из таблицы managers.

Второй параметр метода Execute определяет параметры команды. В нашем случае параметры не используется, поэтому здесь мы указываем специальное значение vtMissing, отмечающее отсутствующий параметр.

И наконец, третий параметр метода Execute указан как константа ADODB::adCmdText. Эта константа определяет, что в первом параметре мы передали методу Execute строку SQL (а не имя хранимой процедуры или таблицы).

После выполнения метод Execute возвращает указатель на интерфейс набора записей ADODB::_RecordsetPtr. Набор записей представляет собой таблицу, созданную в результате выполнения команды.

Работа с набором записей

Извлечение отдельных записей из набора необходимо выполнять в цикле. Здесь используется техника, аналогичная той, что применялась нами в сценариях JScript.

Для проверки условия завершения цикла программа должна анализировать содержимое свойство EOF объекта Recordset:

while(rs->adoEOF == VARIANT_FALSE)
{
  . . .
}

Здесь мы, однако, ссылаемся не на свойство EOF, а на переименованное при импорте библиотеки типов ADO свойство adoEOF. При достижении конца набора записей этой свойство будет содержать значение VARIANT_FALSE.

Извлечение содержимого полей набора записей выполняется следующим образом:

_variant_t vManagerID;
_variant_t vName;

vManagerID  = rs->Fields->GetItem(_variant_t("ManagerID"))->Value;
vName       = rs->Fields->GetItem(_variant_t("Name"))->Value;

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

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

hr = rs->MoveNext();
if(!SUCCEEDED(hr))
  break;

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

Завершив операции над набором записей Recordset, его следует закрыть при помощи метода Close:

rs->Close();

Вызов хранимой процедуры

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

Чтобы передать хранимой процедуре параметры, Вы должны вначале создать объект класса Command (команду), а затем при помощи метода CreateParameter определить все необходимые параметры.

Создание объекта Command выполняется так:

ADODB::_CommandPtr cmd = NULL;
cmd.CreateInstance(__uuidof(ADODB::Command));

Далее надо «привязать» команду к каналу связи с набором данных, установив значение свойства ActiveConnection:

cmd->ActiveConnection = cn;

Так как команда выполняет вызов хранимой процедуры, в свойство CommandType следует записать значение ADODB::adCmdStoredProc:

cmd->CommandType = ADODB::adCmdStoredProc;

В свойство CommandText Вы должны записать имя хранимой процедуры (как переменную класса _bstr_t):

_bstr_t bsCommandText(L"ManagerLogin");
cmd->CommandText = bsCommandText;

Теперь займемся параметрами команды.

Вначале определим указатель на параметр как объект класса ADODB::_ParameterPtr:

ADODB::_ParameterPtr param = NULL;

Теперь мы создаем сам параметр, указывая его имя, тип, значение и размер данных:

_bstr_t bsParamName(L"User");
param = cmd->CreateParameter(
  bsParamName, ADODB::adVarChar, ADODB::adParamInput,
  -1, vtMissing);

Здесь имя параметра задано как User. Константа ADODB::adVarChar определяет, что параметр является текстовой строкой. С помощью константы ADODB::adParamInput мы указываем, что данный параметр — входной. Четвертый параметр метода CreateParameter указывает максимальный размер данных как –1, что означает отсутствие ограничений на этот размер. И, наконец, последний параметр, определяющий значение параметра, указан как vtMissing (то есть пропущен).

Действительное значение входного параметра с именем User мы задаем при помощи метода Append:

_variant_t vName(szName);
param->Value = vName;
cmd->Parameters->Append(param);

Здесь мы вначале инициализируем переменную vName из обычной текстовой строки szName, закрытой двоичным нулем, а затем присваиваем ее значение свойству param->Value. Далее параметр добавляется в набор параметров методом Append.

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

_bstr_t bsParamName1(L"Pass");
param = cmd->CreateParameter(
  bsParamName1, ADODB::adVarChar, ADODB::adParamInput,
  -1, vtMissing);

_variant_t vPassword(szPassword);
param->Value = vPassword;
cmd->Parameters->Append(param);

При создании выходного параметра мы используем константу ADODB::adParamOutput:

_bstr_t bsParamName2(L"Rights");
param = cmd->CreateParameter(
  bsParamName2, ADODB::adVarChar, ADODB::adParamOutput,
  16, vtMissing);
param->Value = _variant_t("");
cmd->Parameters->Append(param);

Далее команда выполняется (в нашем случае происходит запуск хранимой процедуры):

cmd->Execute(&vtMissing, &vtMissing, ADODB::adCmdStoredProc);

После ее завершения значение выходного параметра можно взять из свойства param->Value:

_variant_t ok = param->Value;

Обработка ошибок

В начале этой главы мы рассказывали о том, как выполняется обработка ошибок, возникающих при использовании объектов ADO. Как Вы знаете, ошибки попадают в ADO от провайдера и помещаются в набор Errors, причем в результате выполнения одной команды может возникать сразу несколько ошибок. Для каждой ошибки создается объект Error, который затем помещается в набор Errors.

Серверным сценариям JScript доступны только два свойства объекта Error — number и description, первое из которых содержит числовой код ошибки, а второе — ее текстовое описание.

Программы C++ (так же как сценарии VB Script и программы, написанные на языке Microsoft Visual Basic) получают доступ ко всем свойствам объекта Error, перечисленным в таблице 4-4. Это Description (текст сообщения об ошибке), Number (код ошибки), Source (объект, вызвавший появление ошибки), SQLState (информация об ошибке от источника данных SQL), NativeError (аналогично SQLState), HelpFile (ссылка на файл справочной системы с объяснением ошибки) и HelpContext (идентификатор раздела справочной системы с описанием ошибки).

Если программа C++ обращается к объектам ADO с применением импортирования библиотеки типов ADO посредством оператора #import, то при возникновении ошибочной ситуации исключается класс _com_error. Мы обрабатываем его с помощью конструкции try-catch следующего вида:

try
{
  hr = cn.CreateInstance(__uuidof(ADODB::Connection));
  . . .
  cn->Open(bsConnString, bsUserID, bsUserPwd,
     ADODB::adConnectUnspecified);
  . . .
}
catch(_com_error ex)
{
  AdoErrHandler(cn);
  return;
}

Обработка ошибок выполняется функцией AdoErrHandler, которой в качестве параметра cn передается указатель на интерфейс ADODB::Connection.

В начале своей работы функция AdoErrHandler получает набор Errors, содержащий объекты Error, создаваемые для каждой ошибки:

ADODB::ErrorsPtr Errors = NULL;
Errors = cn->GetErrors();

Этот набор, извлекаемый средствами метода GetErrors, мы будем обрабатывать в цикле, так как объектов Error может быть создано несколько. Для определения количества элементов в наборе Errors нужно использовать метод GetCount:

long   nErrCount;
nErrCount = Errors->GetCount();

Далее извлечение объектов Error можно выполнить следующим образом:

ADODB::ErrorPtr  Error  = NULL;
for(long i = 0; i < nErrCount; i++)
{
  Error = Errors->GetItem((_variant_t)((long)i));
  . . .
  Error->Release();
  Error = NULL;
}

Здесь переменная цикла i принимает значения от 0 до числа элементов в наборе Errors. С помощью метода GetItem мы по очереди извлекаем указатели на интерфейсы объектов Errors, сохраняя их в переменной Error типа ADODB::ErrorPtr. После использования указатель Error освобождается методом Release. Затем мы присваиваем ему значение NULL.

Теперь для каждой ошибки (то есть для каждого извлеченного объекта Error) мы должны получить значения свойств. Эта операция выполняется с использованием методов, предусмотренных в объекте Error:

CString strNumber;
strNumber.Format("Number: %x", Error->GetNumber());

Здесь мы извлекли номер ошибки методом GetNumber, преобразовали его в текстовую строку и записали в строчную переменную strNumber класса Cstring.

Аналогичные действия можно предпринять и для других свойств объекта Error:

strDescription.Format("Description: %s",  
  (LPCTSTR)_bstr_t(Error->GetDescription()));

strSource.Format("Source: %s",
  (LPCTSTR)_bstr_t(Error->GetSource()));

strHelpFile.Format("HelpFile: %s", 
  (LPCTSTR)_bstr_t(Error->GetHelpFile()));

strHelpContext.Format("HelpContext: %ld", Error->GetHelpContext());

strNativeError.Format("NativeError: %ld", Error->GetNativeError());

strSQLState.Format("SQLState:  %s",
  (LPCTSTR)_bstr_t( Error->GetSQLState()));

Полный текст функции обработки ошибок AdoErrHandler приведен в листинге 4-57.

Пример программы

В качестве примера приложения, написанного на C++ и обращающегося к ADO с применением импортирования библиотеки типов ADO, рассмотрим простую консольную программу просмотра записей таблицы managers из базы данных BookStore. Эту базу мы создали в начале главы.

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

Login name: frolov
Password: 123

Manager list:

1 |    frolov | 123 | 11.12.99 12:24:50 | Administrator
2 |    Petrov | 123 |              NULL | sh_manager
3 |  Sidoroff | 123 |              NULL | Sales_manager
5 |TestAdmins |1234 |              NULL | Sales_manager
4 |     admin | 123 | 05.12.99 09:50:10 | Sales_manager

Если же идентификатор или пароль введены неправильно либо если данный пользователь не обладает правами администратора, то на консоли появляется лишь сообщение о запрете доступа:

Login name: frolov
Password: 111

Access denied

Рассмотрим исходные тексты программы.

Листинг файла stdafx.h, создаваемого автоматически системой разработки Microsoft Visual C++, мы не будем приводить ради экономии места. В этот файл мы добавили вручную строки импорта библиотеки типов ADO версии 2.0, как это показано ниже:

#import "d:\program files\common files\system\ado\Msado20.tlb" \
  rename ("EOF", "adoEOF")

В листинге 4-56 приведен исходный текст самой программы.

Листинг 4-56 хранится в файле ch4\CPPADO\CPPADO.cpp на прилагаемом к книге компакт-диске.

Глобальная переменная com_init класса ComInit предназначена для инициализации системы COM, а также для освобождения ее ресурсов, связанных с данной программой после завершения ее работы:

struct ComInit
{
  ComInit()
  {
     ::CoInitialize(NULL);
  }

  ~ComInit()
  {
     ::CoUninitialize();
  }
} com_init;

Функция _tmain, получающая управление после запуска приложения, вызывает функцию login, определенную в нашей программе:

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
  int nRetCode = 0;
  if(!AfxWinInit(::GetModuleHandle(NULL), NULL,
     ::GetCommandLine(), 0))
  {
     cerr << _T("Ошибка инициализации MFC") << endl;
     nRetCode = 1;
  }
  else
  {
     if(login())
     {
       cout << _T("\nManager list: \n\n");
       getManagers();
     }
     else
       cout << _T("\nAccess denied\n");
  }

  return nRetCode;
}

Функция login запрашивает с консоли идентификатор и пароль пользователя, ищет его в базе данных, извлекает и проверяет права. Если пользователь зарегистрирован и обладает правами администратора, функция login возвращает значение true, а если нет — false.

Для администратора функция _tmain вызывает функцию getManagers, извлекающую из базы данных и отображающую информацию о сотрудниках магазина.

Функция login

Рассмотрим исходный текст функции login.

В блоке try эта функция создает объект ADODB::Connection и открывает соединение:

ADODB::_ConnectionPtr cn = NULL;
HRESULT hr = S_OK;

try
{
  hr = cn.CreateInstance(__uuidof(ADODB::Connection));

  if(!SUCCEEDED(hr))
     return false;

  _bstr_t bsConnString(L"DSN=BookStore");
  _bstr_t bsUserID(L"dbo");
  _bstr_t bsUserPwd(L"");

  cn->Open(bsConnString, bsUserID, bsUserPwd,
     ADODB::adConnectUnspecified);
  . . .
}

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

ADODB::_CommandPtr cmd = NULL;
cmd.CreateInstance(__uuidof(ADODB::Command));

cmd->ActiveConnection = cn;
cmd->CommandType = ADODB::adCmdStoredProc;

_bstr_t bsCommandText(L"ManagerLogin");
cmd->CommandText = bsCommandText;

ADODB::_ParameterPtr param = NULL;

_bstr_t bsParamName(L"User");

param = cmd->CreateParameter(
  bsParamName, ADODB::adVarChar, ADODB::adParamInput,
  -1, vtMissing);

CHAR szName[100];
cout << "\nLogin name: ";
cin >> szName;
_variant_t vName(szName);

param->Value = vName;
cmd->Parameters->Append(param);

_bstr_t bsParamName1(L"Pass");

param = cmd->CreateParameter(
  bsParamName1, ADODB::adVarChar, ADODB::adParamInput,
  -1, vtMissing);

CHAR szPassword[100];
cout << "Password: ";
cin >> szPassword;
_variant_t vPassword(szPassword);

param->Value = vPassword;
cmd->Parameters->Append(param);

_bstr_t bsParamName2(L"Rights");

param = cmd->CreateParameter(
  bsParamName2, ADODB::adVarChar, ADODB::adParamOutput,
  16, vtMissing);

param->Value = _variant_t("");
cmd->Parameters->Append(param);

cmd->Execute(&vtMissing, &vtMissing, ADODB::adCmdStoredProc);

Идентификатор и пароль запрашиваются из стандартного потока ввода, связанного с клавиатурой.

Определение возвращаемого значения выполняется путем простого сравнения значения выходного параметра и текстовой строки Administrator:

_variant_t ok = param->Value;
_variant_t okAdmin(L"Administrator");

if(ok == okAdmin)
  return true;
else
  return false;

Заметим, что оператор сравнения перегружен в классе _variant_t, поэтому такая операция выполняется очень просто.

При возникновении ошибочных ситуаций управление передается в блок catch, где выполняется вызов функции обработки ошибок AdoErrHandler:

try
{
  . . .
}
catch(_com_error ex)
{
  AdoErrHandler(cn);
  return false;
}

Так как ошибки «привязаны» к соединению с источником данных, в качестве параметра этой функции передается указатель на интерфейс нашего источника данных cn.

Функция getManagers

Эта функция получает содержимое таблицы managers не с помощью хранимой процедуры, а выполняя строку SQL с оператором SELECT:

ADODB::_ConnectionPtr cn = NULL;
ADODB::_RecordsetPtr rs = NULL;

try
{
  HRESULT hr = S_OK;
  hr = cn.CreateInstance(__uuidof(ADODB::Connection));

  if(!SUCCEEDED(hr))
     return;

  _bstr_t bsConnString(L"DSN=BookStore");
  _bstr_t bsUserID(L"dbo");
  _bstr_t bsUserPwd(L"");

  cn->Open(bsConnString, bsUserID, bsUserPwd,
     ADODB::adConnectUnspecified);

  _bstr_t bsCommand(L"select * from managers");
  rs = cn->Execute(bsCommand, &vtMissing, ADODB::adCmdText);
  . . .
}

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

_variant_t vManagerID;
_variant_t vName;
_variant_t vPassword;
_variant_t vLastLogin;
_variant_t vRights;

CString strTmp   = "";

while(rs->adoEOF == VARIANT_FALSE)
{
  vManagerID =
     rs->Fields->GetItem(_variant_t("ManagerID"))->Value;
  vName =
     rs->Fields->GetItem(_variant_t("Name"))->Value;
  vPassword =
     rs->Fields->GetItem(_variant_t("Password"))->Value;
  vLastLogin =
     rs->Fields->GetItem(_variant_t("LastLogin"))->Value;
  vRights =
     rs->Fields->GetItem(_variant_t("Rights"))->Value;

  strTmp.Format("%s | %10s | %10s | %20s | %10s",
     v2str(vManagerID), v2str(vName),
     v2str(vPassword), v2str(vLastLogin), v2str(vRights));

  cout << (LPCTSTR)strTmp << "\n";

  hr = rs->MoveNext();
  if(!SUCCEEDED(hr))
     break;
}

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

Исходный текст функции обработки ошибок AdoErrHandler приведен в листинге 4-57. Мы уже рассказывали раньше об использованных в нем приемах извлечения кодов ошибок.

Листинг 4-57 Вы найдете в файле ch4\CPPADO\adoerrhandler.cpp на прилагаемом к книге компакт-диске.

Служебная функция v2str (листинг 4-58) нужна для преобразования значений типа VARIANT в текстовые строки.

Листинг 4-58 Вы найдете в файле ch4\CPPADO\vcrack.cpp на прилагаемом к книге компакт-диске.

Эта функция получает входной параметр как ссылку на переменную класса COleVariant, предусмотренного в MFC для работы с переменными типа VARIANT. Результат преобразования возвращается как текстовая переменная класса CString.

Функция v2str устроена очень просто.

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

Обратите внимание на применение функций COleCurrency и COleDateTime, помогающих выполнять такое преобразование для денежных данных и для значений дат:

CString v2str(const COleVariant& var)
{
  CString strRet;
  strRet = _T("");
  switch(var.vt)
  {
     case VT_EMPTY:
     case VT_NULL:
       strRet = _T("NULL");
       break;
     case VT_I2:
       strRet.Format(_T("%hd"),V_I2(&var));
       break;
     . . .
     case VT_CY:
       strRet = COleCurrency(var).Format();
       break;
     case VT_DATE:
       strRet = COleDateTime(var).Format(_T("%d.%m.%y %H:%M:%S"));
       break;
     case VT_BSTR:
       strRet = V_BSTR( &var );
       break;
     . . .
     default:
       strRet = _T("not supported");
       break;
     }
  return strRet;
}

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

Вызов ADO через функции Win32

Если Вам больше нравится вызывать интерфейсы элементов ActiveX средствами программного интерфейса Win32, то Вы можете обойтись без импортирования библиотеки типов (хотя этот способ предпочтительнее). В этом разделе мы расскажем о том, как обращаться к методам и свойствам объектов ADO без применения оператора #import.

Обращение к интерфейсам и методам ADO

Рассмотрим некоторые приемы обращения к интерфейсам и методам ADO, основанные на использовании функций программного интерфейса Win32, предназначенных для работы с объектами COM (таких, как CoCreateInstance).

Инициализация COM и переменных BSTR

Как и в предыдущем случае, для работы с объектами ADO Вам необходимо проинициализировать систему COM вызовом функции CoInitialize, а перед завершением программы надо освободить ресурсы COM при помощи функции CoUninitialize.

Отказавшись от импортирования библиотеки типов, мы не сможем воспользоваться для создания строк типа BSTR классом _bstr_t. Поэтому нам придется позаботиться об инициализации и освобождении ресурсов, связанных с такими строками. Эти операции удобно выполнять вместе с инициализацией и освобождением ресурсов COM в конструкторе и деструкторе специального объекта инициализации.

Например, для инициализации строки параметров подключения к источнику данных мы вначале создаем глобальную переменную strAccessConnect типа CString и инициализируем ее следующим образом:

CString strAccessConnect("DSN=BookStore;UID=dbo;PWD=;" );

Далее в конструкторе глобального объекта инициализации мы выделяем память для строки bstrAccessConnect и записываем в нее данные из строки strAccessConnect:

BSTR bstrAccessConnect;
bstrAccessConnect = strAccessConnect.AllocSysString();

Перед завершением работы программы деструктор объекта инициализации освобождает ресурсы, связанные со строкой bstrAccessConnect:

SysFreeString(bstrAccessConnect);

Установка соединения с источником данных

Для установки соединения с источником данных нам необходимо создать объект Connection. Так как мы отказались от импортирования библиотеки типов ADO, нам придется выполнять эту операцию с помощью функции CoCreateInstance:

#include <initguid.h>
#include "adoid.h"
#include "adoint.h"
. . .
ADOConnection* cn  = NULL;
HRESULT hr = S_OK;

hr = CoCreateInstance(CLSID_CADOConnection, NULL,
  CLSCTX_INPROC_SERVER, IID_IADOConnection, (LPVOID*)&cn);

Обратите внимание, что для определения глобальных уникальных идентификаторов ADO, его классов и констант мы включили в исходный текст нашей программы файлы adoid.h и adoint.h. Файл initguid.h должен быть включен только в один файл Вашего проекта.

Создавая объект Connection, функция CoCreateInstance записывает указатель на интерфейс этого объекта в переменную cn типа ADOConnection*. Результат выполнения операции сохраняется в переменной hr типа HRESULT. Так как при отказе от импортирования библиотеки типов ADO механизм обработки исключений _com_error от объектов ADO не используется, Ваше приложение должно проверять коды завершения вызываемых функций и методов.

После создания объекта Connection необходимо открыть соединение. Для этого вначале нужно вызвать метод put_ConnectionString для записи строки параметров соединения, а затем вызвать метод Open, определенный в объекте Connection:

if(SUCCEEDED(hr))
  hr = cn->put_ConnectionString(bstrAccessConnect);

if(SUCCEEDED(hr))
  hr = cn->Open(bstrEmpty, bstrEmpty, bstrEmpty,
     adConnectUnspecified);

Так как все параметры соединения устанавливаются методом put_ConnectionString, мы указали для первых трех параметров метода Open пустые значения bstrEmpty. Строка bstrEmpty определена как пустая строка:

CString strEmpty("");
BSTR bstrEmpty;
bstrEmpty = strEmpty.AllocSysString();

Последний параметр метода Open задает синхронный режим открытия канала связи с источником данных.

Выполнение команды

Прежде чем выполнить команду, наша программа должна создать объект Command, вызвав для этого функцию CoCreateInstance:

ADOCommand* cmd = NULL;
if(SUCCEEDED(hr))
  hr = CoCreateInstance(CLSID_CADOCommand, NULL,
     CLSCTX_INPROC_SERVER, IID_IADOCommand, (LPVOID*)&cmd);

В случае успеха ссылка на интерфейс команды записывается в переменную cmd типа ADOCommand*.

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

if(SUCCEEDED(hr))
  hr = cmd->putref_ActiveConnection(cn);

И наконец, текст команды записывается методом put_CommandText:

if(SUCCEEDED(hr))
  hr = cmd->put_CommandText(bstrCommand);

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

CString strCommand("select * from managers");
BSTR bstrCommand;
bstrCommand = strCommand.AllocSysString();

Теперь мы можем выполнять команду при помощи метода Execute:

ADORecordset* rs = NULL;
if(SUCCEEDED(hr))
  hr = cmd->Execute(&vtEmpty, &vtEmpty2, adCmdText, &rs);

Параметр adCmdText указывает, что команда представляет собой строку программы SQL. В результате выполнения команды будет создан набор записей Recordset, причем указатель на интерфейс соответствующего объекта будет записан в переменную rs типа ADORecordset*.

Работа с набором записей

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

Для проверки условия завершения цикла программа должна вызывать метод get_EOF, определенный в объекте Recordset:

VARIANT_BOOL bEOF = VARIANT_FALSE;
if(SUCCEEDED(hr))
  hr = rs->get_EOF(&bEOF);

При достижении конца набора записей этот метод вернет значение, равное константе VARIANT_FALSE.

Цикл обработки набора записей выглядит, например, так:

while(bEOF == VARIANT_FALSE)
{
  . . .
  hr = rs->get_EOF(&bEOF);
  if(!SUCCEEDED(hr))
     break;
}

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

Извлечение и обработка отдельных записей выполняется следующим образом.

Вначале при помощи метода get_Fields мы получаем указатель на интерфейс набора Fields:

  ADOFields* adoFields = NULL;
  hr = rs->get_Fields(&adoFields);
  if(!SUCCEEDED(hr))
     break;

На следующем этапе мы вызываем через этот интерфейс методы get_Item и get_Value для каждого поля обрабатываемой записи:

ADOField* fldManagerID = NULL;
ADOField* fldName = NULL;

hr = adoFields->get_Item(COleVariant("ManagerID"),
  &fldManagerID);
if(!SUCCEEDED(hr))
  break;

hr = fldManagerID->get_Value(&vManagerID);
if(!SUCCEEDED(hr))
  break;

hr = adoFields->get_Item(COleVariant("Name"), &fldName);
if(!SUCCEEDED(hr))
  break;

hr = fldName->get_Value(&vName);
if(!SUCCEEDED(hr))
  break;

Здесь мы извлекли содержимое полей идентификатора сотрудника ManagerID и его имени Name.

Чтобы перейти к обработке следующей записи набора, мы вызываем метод MoveNext:

hr = rs->MoveNext();
if(!SUCCEEDED(hr))
  break;

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

Пример программы

В этом разделе мы приведем несложный пример программы, обращающей к ADO без импортирования библиотеки типов. Эта программа, так же как и предыдущая, показывает в консольном окне содержимое таблицы сотрудников managers. Для сокращения листинга мы не стали проверять идентификатор и пароль пользователя, запускающего эту программу.

Исходный текст программы Вы найдете в листинге 4-59.

Листинг 4-59 хранится в файле ch4\CPPADO1\CPPADO1.cpp на прилагаемом к книге компакт-диске.

Переменные strAccessConnect, strEmpty и strCommand класса CString предназначены для инициализации строчных переменных класса BSTR с именами bstrAccessConnect, bstrEmpty и bstrCommand соответственно:

CString strAccessConnect("DSN=BookStore;UID=dbo;PWD=;" );
BSTR bstrAccessConnect;

CString strEmpty("");
BSTR bstrEmpty;

CString strCommand("select * from managers");
BSTR bstrCommand;

В строке bstrAccessConnect записана строка параметров, необходимых для подключения к источнику данных. Переменная bstrEmpty представляет собой пустую строку, а переменная bstrCommand содержит строку SQL, с помощью которой мы получим все записи из таблицы managers.

Кроме того, нам потребуются две пустые переменные vtEmpty и vtEmpty2 класса VARIANT:

VARIANT   vtEmpty;
VARIANT   vtEmpty2;

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

struct ComInit
{
  ComInit()
  {
     ::CoInitialize(NULL);

     bstrAccessConnect = strAccessConnect.AllocSysString();
     bstrEmpty = strEmpty.AllocSysString();
     bstrCommand = strCommand.AllocSysString();
     vtEmpty.vt    = VT_ERROR;
     vtEmpty.scode = DISP_E_PARAMNOTFOUND;
     vtEmpty2.vt   = VT_ERROR;
     vtEmpty2.scode = DISP_E_PARAMNOTFOUND;
  }

  ~ComInit()
  {
     ::CoUninitialize();

     SysFreeString(bstrAccessConnect);
     SysFreeString(bstrEmpty);
     SysFreeString(bstrCommand);
  }
} com_init;

В задачу конструктора класса входит вызов уже знакомой Вам функции CoInitialize, а также инициализация перечисленных выше переменных классов BSTR и VARIANT. Деструктор вызывает функцию CoUninitialize, а затем при помощи функции SysFreeString освобождает ресурсы, выделенные для строк класса BSTR.

Отображение содержимого таблицы managers выполняется функцией getManagers, получающей управление от функции _tmain после запуска нашей программы и инициализации библиотеки классов MFC.

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
  int nRetCode = 0;
  if(!AfxWinInit(::GetModuleHandle(NULL), NULL,
     ::GetCommandLine(), 0))
  {
     cerr << _T("Ошибка инициализации MFC") << endl;
     nRetCode = 1;
  }
  else
     getManagers();

  return nRetCode;
}

В области локальных переменных функции getManagers определены указатели на интерфейсы cn, rs и cmd:

ADOConnection*cn = NULL;
ADORecordset* rs = NULL;
ADOCommand* cmd  = NULL;

В переменную cn записывается указатель на интерфейс Connection, для чего используется функция CoCreateInstance, создающая объект указанного класса:

HRESULT hr = S_OK;
hr = CoCreateInstance(CLSID_CADOConnection, NULL,
  CLSCTX_INPROC_SERVER, IID_IADOConnection, (LPVOID*)&cn);

Если данный объект был успешно создан, мы записываем в свойство ConnectionString объекта Connection строку параметров соединения, вызывая для этого метод put_ConnectionString:

if(SUCCEEDED(hr))
  hr = cn->put_ConnectionString(bstrAccessConnect);

Далее соединение с источником данных открывается методом Open. Так как все параметры соединения уже записаны в свойство ConnectionString, мы указываем первые три параметра метода Open как пустые, передавая через них строку bstrEmpty:

if(SUCCEEDED(hr))
  hr = cn->Open(bstrEmpty, bstrEmpty, bstrEmpty,
     adConnectUnspecified);

На следующем этапе происходит создание объекта Command, для чего мы опять применяем функцию CoCreateInstance, но с другими параметрами. Указатель на интерфейс созданного объекта команд записывается в переменную cmd:

if(SUCCEEDED(hr))
  hr = CoCreateInstance(CLSID_CADOCommand, NULL,
     CLSCTX_INPROC_SERVER, IID_IADOCommand, (LPVOID*)&cmd);

Чтобы связать объект Command и соединение, мы записываем указатель на интерфейс соединения в свойство ActiveConnection объекта Connection, вызывая метод putref_ActiveConnection:

if(SUCCEEDED(hr))
  hr = cmd->putref_ActiveConnection(cn);

Текст команды записывается методом put_CommandText в соответствующее свойство объекта Command:

if(SUCCEEDED(hr))
  hr = cmd->put_CommandText(bstrCommand);

Теперь можно выполнять команду, вызывая метод Execute. В качестве первых двух параметров мы передаем этому методу пустые переменные класса VARIANT. Третий параметр определяет, что необходимо выполнить строку SQL, заданную в свойстве CommandText объекта Command. И наконец, через последний параметр методу Execute передается адрес переменной, в которую будет записан указатель на объект класса Recordset, содержащий набор записей, извлеченных из таблицы managers:

if(SUCCEEDED(hr))
  hr = cmd->Execute(&vtEmpty, &vtEmpty2, adCmdText, &rs);

После выполнения команды в цикле при помощи рассмотренной ранее методики извлекаются отдельные записи набора Recordset, и результат выводится на консоль.

Перед запуском цикла мы определяем переменные класса ColeVariant, в которые будут записаны значения, извлеченные из строк таблицы:

COleVariant vManagerID;
COleVariant vName;
COleVariant vPassword;
COleVariant vLastLogin;
COleVariant vRights;

Кроме того, мы определяем рабочую переменную strTmp класса CString, а также переменную bEOF, которая будет использована для обнаружения конца набора записей:

CString strTmp   = "";
VARIANT_BOOL bEOF = VARIANT_FALSE;

Далее мы извлекаем признак конца набора записей при помощи метода get_EOF:

if(SUCCEEDED(hr))
  hr = rs->get_EOF(&bEOF);

В переменной adoFields находится указатель на интерфейс объекта Fields, с помощью которого мы извлечем записи из набора:

ADOFields* adoFields = NULL;

Следующие переменные предназначены для хранения указателей на интерфейсы объектов Filed, создаваемых для хранения содержимого отдельных полей текущей записи набора Recordset:

ADOField* fldManagerID = NULL;
ADOField* fldName = NULL;
ADOField* fldPassword = NULL;
ADOField* fldLastLogin = NULL;
ADOField* fldRights = NULL;

Сам цикл организован следующим образом:

while(bEOF == VARIANT_FALSE)
{
  hr = rs->get_Fields(&adoFields);
  if(!SUCCEEDED(hr))
     break;

  hr = adoFields->get_Item(COleVariant("ManagerID"),
     &fldManagerID);
  if(!SUCCEEDED(hr))
     break;

  hr = fldManagerID->get_Value(&vManagerID);
  if(!SUCCEEDED(hr))
     break;

  hr = adoFields->get_Item(COleVariant("Name"), &fldName);
  if(!SUCCEEDED(hr))
     break;

  hr = fldName->get_Value(&vName);
  if(!SUCCEEDED(hr))
     break;

  hr = adoFields->get_Item(COleVariant("Password"), &fldPassword);
  if(!SUCCEEDED(hr))
     break;

  hr = fldPassword->get_Value(&vPassword);
  if(!SUCCEEDED(hr))
     break;

  hr = adoFields->get_Item(COleVariant(
     "LastLogin"), &fldLastLogin);
  if(!SUCCEEDED(hr))
     break;

  hr = fldLastLogin->get_Value(&vLastLogin);
  if(!SUCCEEDED(hr))
     break;

  hr = adoFields->get_Item(COleVariant("Rights"), &fldRights);
  if(!SUCCEEDED(hr))
     break;

  hr = fldRights->get_Value(&vRights);
  if(!SUCCEEDED(hr))
     break;

  strTmp.Format("%s | %10s | %10s | %20s | %10s",
     v2str(vManagerID), v2str(vName),
     v2str(vPassword), v2str(vLastLogin),  v2str(vRights));

  cout << (LPCTSTR)strTmp << "\n";

  hr = rs->MoveNext();

  if(!SUCCEEDED(hr))
     break;

  hr = rs->get_EOF(&bEOF);
  if(!SUCCEEDED(hr))
     break;
}

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

Затем мы по очереди извлекаем поля текущей записи методом get_Item, а затем и значения полей при помощи метода get_Value.

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

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

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

rs->Close();
cn->Close();

[Назад] [Содержание] [Дальше]