Статья: Web Services и MS Visual FoxPro Часть 1

advertisement
Статья: Web Services и MS Visual FoxPro Часть 1
В этой статье :
 Для чего написана эта статья.
 Что такое Web Services.
 Немного об XML и “заточки под него VFP”.
 Простой пример получения данных посредством WS из
Интернета.
Для чего написана эта статья.
В последнее время все чаще можно услышать разговоры о том, что
FoxPro - это устаревшая технология 80-х, что программисты, которые
остались верны данной среде разработки "отстали от жизни", что у них
"нет будущего" и, вообще, "они ничего не понимают в развитии IT
индустрии"... Прочитав эту статью, Вы научитесь применять FoxPro в
глобальных масштабах всего мира. Для Вас будет абсолютно без
разницы, для чего писать приложения - для маленькой задачи на одном
компьютере, для целого предприятия, работающего в среде Интранет,
для крупной компании, спрятавшей своих клиентов за навороченными
системами защиты "firewall", или для ее филиалов, разбросанных по
всему миру, и имеющих разнородный парк серверов, на которых
крутятся разношерстные операционные системы от Windows 2000, 2003
до Unix. Web Services размывают наши представления о различиях
между файл - серверными и клиент - серверными приложениямию.
Теперь Вы можете передавать между компьютерами только минимально
необходимый набор информации, применяя хранимые процедуры и
транзакции на удаленном сервере данных, тем самым лишая многих
критиков FoxPro основных аргументов против применения данной среды
в современных надежных и легко масштабируемых приложениях (так
как в этом случае Вам не надо ничего будет менять в клиентском
приложении - только изменить немного WS).
Причем, Вы неожиданно откроете для себя, что такие феноменальные
возможности появились у старого и доброго FoxPro еще много лет назад
в 7 версии, намного опережая продвинутые продукты конкурентов и
даже многие продукты самого Microsoft (например, SQL Server 2000).
Проблема была только в Вас самих - Вы этим не пользовались. Мы очень
надеемся, что данная статья Вам поможет пересмотреть Ваши взгляды на
Ваш привычный процесс разработки приложений, дав толчок к
внедрению новых технологий у Ваших клиентов и, в конечном итоге, к
upgrade Ваших skills. В данной публикации мы попытаемся "объять
необъятное", разумеется, с Вашей помощью в виде отзывов, которые
помогут улучшить понимание данного материала и сделать его более
понятным для тех, кто только начинает свой путь по нелегкой тропе к
вершинам программистского мастерства.
Что такое Web Services.
В начале моей нелегкой жизни профессионального программиста
клиенты часто просили меня рассказать, как работает написанная мной
программа. Мои объяснения сводились примерно к следующему: “Вы
нажимаете кнопку "вывести на экран справочник товара", и компьютер
посылает запрос к серверу в виде строки, в которой указывает то, что
Вам надо. Далее сервер обрабатывает Ваш результат, формирует
ответную строку и посылает ее назад. Моя программа принимает ответ и
показывает Вам в удобочитаемом виде этот самый справочник...” Все
были довольны: клиент тем, что понял, а я, что удалось ему объяснить.
Только через несколько лет я обнаружил, что это была голая ложь по
отношению к файл – серверу. Даваемое мной описание подходит больше
под КС (клиент-серверное приложение), а в идеале, на приложение, где
источником данных является Web Service. Так что же все-таки это
непонятное импортное словосочетание означает на самом деле? Цель
моей скромной статьи - приоткрыть завесу тайны и мистики над этой
технологией, которой пророчат большое будущее как Microsoft, так и их
конкуренты.
С технической точки зрения Web Services (сокращенно WS) - это DLL,
установленная на Вашем Web Server, которая может быть вызвана
другими компьютерами, находящимися как во внутренней сети Intranet,
так и во внешней сети Internet. Методы в этой DLL могут делать то же
самое, что и COM сервер. Если объяснять простыми словами, то Web
Service просто очень маленькая программка, которая прослушивает
определенный порт в IIS (Internet Information Server, или в
простонародии “Сервер WEB”). Как мы, наверное, с Вами знаем, каждый
WEB сервер имеет адрес IP (например, у локального 127.0.0.1) и порт
(для Вашего WEB Server по умолчанию 80 ). Когда мы с Вами набираем,
например, http://www.sergey.co.uk/ , то наш Browser (программа, с
помощью которой Вы “гуляете по Интернету”) посылает Ваш запрос к
DNS серверу, который говорит, что для данного адреса будет следуюший
IP - 62.105.65.69, и Вы направляетесь прямо туда, где находится мой
сайт. Если теперь указать имя конкретного файла, имеющего
расширение .WSDL, то IIS поймет, что запрос предназначен для WS,
разбудит его, и последний начнет обработку нашего запроса, как самая
обычная программа, запущенная удаленно – то есть на чужом удаленном
(remote) компьютере. Передав параметры WS, Вы можете запросить
только интересующую Вас информацию, например, курсы валют на
определенную дату, список книг в библиотеке по автору, список
неоплаченных счетов... Вот собственно и все. Просто, как "Автомат
Калашникова".
Немного об XML и “заточки под него VFP”.
Итак, если мы немного знакомы с Интернет - технологиями, то обмен
данными там ведется с помощью протокола HTTP, который, как известно,
ничего кроме символьных строк не понимает... Как же "выкрутиться" из
данной ситуации, ведь нам надо передавать цифры, спецсимволы и даже
картинки с двоичными данными? Для этого люди придумали XML
(Extensible Markup Language). Для тех, кто любит изучать вещи
досконально, советую посетить страничку http://www.w3.org/XML/ , где
можно узнать последние новости об этом языке. Мне, как программисту
баз данных, достаточно знать только то, что каждое выражение (как и в
случае c HTML) окружено тэгами, причем один должен быть
открывающим, а второй обязательно должен закрывать выражение.
Рассмотрим простой пример в FoxPro, в котором полная поддержка XML
появилась еще в 7 версии (просто выделяете данный текст программы и
коприруете его в открытое окно редактора FoxPro):
* создаем простую таблицу
* не забыв при этом в директории проекта иметь файл config.fpw
* где есть строка: codepage=1251
* в этом случае все создаваемые нами файлы будут иметь
* корректную кодовую страницу для работы с русским языком
SET SAFETY OFF
CREATE TABLE TESTXML.DBF FREE (text1 c(40), number1 N(4), memo1 M)
* вносим несколько значений
INSERT INTO TESTXML (text1,number1,memo1) VALUES ;
('Первая строка', 1233,'Строка в Memo поле первая'+CHR(13)+CHR(10)+;
'Вот и вторая строка в Memo')
INSERT INTO TESTXML (text1,number1,memo1) VALUES ;
('Еще одна строка', 1963,'FoxPro отличает от других средства разработки'+CHR(13)+CHR(10)+;
'приложений глубокая продуманность языка для реальной жизни.')
*
* создаем файл XML из нашей таблицы
* думаю, что описание команды CURSORTOXML Вы сможете прочитать в Help
* Остановлюсь только на параметрах, примененных мною
* "TESTXML" - имя открытой таблицы
* "myXMLfile.xml" - имя файла, в который мы хотим записать полученный результат
* 1 - создать "Element-centric XML" в этом случае получаем "правильный" XML файл
* +8 - обернуть Memo поле в CDATA - это значит, что можно передавать и двоичные значения
* +16 - использовать кодовую страницу курсора (в нашем случае 1251 - Russian Windows)
* +512 - вывести результат в файл
* 0 - берем все записи
* "1" - схема данных должна быть встроена в файл. То есть как бы словарь данных
*
передается вместе с данными и в месте приема мы их правильно расшифровываем.
CURSORTOXML("TESTXML","myXMLfile.xml",1,8+16+512,0,"1")
* теперь просмотрим полученный файл
MODIFY FILE ("myXMLfile.xml")
* почистим за собой
CLOSE DATABASES
DELETE FILE TESTXML.DBF
DELETE FILE TESTXML.FPT
DELETE FILE myXMLfile.XML
example01.prg
В результате работы программы получаем следующий файл, который Вы
должны увидеть на своих экранах:
<?xml version = "1.0" encoding="Windows-1251" standalone="yes"?>
<VFPData>
<xsd:schema id="VFPData" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemasmicrosoft-com:xml-msdata">
<xsd:element name="VFPData" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="testxml" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="text1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="40"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="number1">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="4"/>
<xsd:fractionDigits value="0"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="memo1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="2147483647"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:anyAttribute namespace="http://www.w3.org/XML/1998/namespace" processContents="lax"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<testxml>
<text1>Первая строка</text1>
<number1>1233</number1>
<memo1><![CDATA[Строка в Memo поле первая
Вот и вторая строка в Memo]]></memo1>
</testxml>
<testxml>
<text1>Еще одна строка</text1>
<number1>1963</number1>
<memo1><![CDATA[FoxPro отличает от других средств разработки
приложений глубокая продуманность языка для реальной жизни.]]></memo1>
</testxml>
</VFPData>
Полученный результат грубо можно разбить на три части. В первой части
(строке) идет объяснение, что это такое. Из всего этого нас интересует
только encoding="Windows-1251", что собственно говорит о том, что мы
правильно получили кодировку русского языка, и при приеме
информации у нас не должно возникнуть проблем.
Во второй части идет описание формата данных. Можно очень грубо
провести аналогию с заголовком таблиц DBF самого Visual FoxPro. Типы
данных могут немного отличаться от привычных нам, так, например, для
поля Memo - это все тот же string, но с очень большой длиной. Одно из
преимуществ формата XML эксперты называют "human readable" - то
есть возможность человека читать его и понимать. Я очень надеюсь, что
Вы принадлежите к человечеству и, соответственно, сможете понять, что
там есть.
Третья часть, собственно, данные. Они будут зависеть от того, что Вы
туда включили. В общем-то ничего сложного, я только хочу обратить
внимание на загадочные буквы CDATA вокруг Memo полей. FoxPro
позволяет зашифровывать данные на лету в формат base64, который
позволяет "оборачивать" двоичные файлы и передавать их в виде все
тех же привычных для HTTP символов. По примерно такому же принципу
мы передаем в электронной почте всевозможные картинки, музыку и
даже видео.
Совсем не плохо для практически всего одной команды FoxPro!
Простой пример получения данных посредством WS из
Интернета.
Давайте теперь, наконец, сделаем что-то практически, что - то "крутое"
и глобальное. Для следующего примера Вам понадобится связь с
внешним миром, то есть Internet. Подойдет даже очень медленное
модемное соединение. Усложним пример тем, что источником данных
будет MS SQL Server, а Web Services будут построены на основе
любимого и революционного детища компании Microsoft - ASP.NET
(если иметь в виду некоторые принципы, заложенные в основу ASP.NET,
как, например, создание предкомпилированного кода, то эта
возможность была уже применена в FoxBase 20 лет назад). То есть
данный пример покажет истинную независимость Вас, как разработчика,
от среды.
Итак, вот код, который Вам предлагается скопировать в окно редактора
FoxPro и запустить:
***********
* пример получения новостей с сайта www.sergey.co.uk
***********
SET TALK OFF
* создаем объект на основе пакета SOAP 3.0
o=CREATEOBJECT("MSSoap.SoapClient30")
* соединяемся с удаленным источником данных
* в качестве параметра стандартного метода MSSoapInit задается URL
o.MSSoapInit("http://www.sergey.co.uk/WebModules/NewsManaer/Headlines.asmx?WSDL")
* вызываем функцию данного Web Service как самую обычную функцию FoxPro
* и не важно, что объект расположен от Вас за тысячи километров
* Данная функция запрашивет заголовки новостей
*o.getheadlines(1)
loexception=NULL && устанавливаем объект, в котором будем отлавливать ошибки
tt=''
* применяем конструкцию TRY..CATCH чтобы возможные проблемы не повлияли на наше
* приложение. В принципе все можно было "упрятать" в эту конструкцию
TRY
tt=o.getheadlines(1)
CATCH TO loexception && если будет ошибка, то программа нам ее распечатает
? loexception.MESSAGE
? loexception.ERRORNO
ENDTRY
*? tt && если убрать здесь комментарий то на экране Вы увидите в случае успеха тип (Object)
IF ISNULL(loexception) && если не было ошибок, то продолжим
lcXML=tt.ITEM(0).parentnode.XML &&
* ? lcXML && если убрать здесь комментарий то на экране Вы увидите принятый Вами XML файл
* STRTOFILE(lcXML,'aaa.xml') && можно записать XML в файл на диск
* для дальнейшей работы создаем XML Adapter
LOCAL oXA AS XMLADAPTER
oXA = CREATEOBJECT("XMLAdapter")
* загружаем полученную информацию в созданный адаптер
oXA.LOADXML(lcXML,.F.,.T.)
IF USED('NEWS')
USE IN NEWS
ENDIF
* создаем таблицу из адаптера с названием NEWS
oXA.TABLES[1].TOCURSOR(.F.,"NEWS")
BROWSE && смотрим, что получили
ENDIF
* как обычно чистим за собой
RELEASE o
example02.prg
Программа собственно выполняет простую функию - запрашивает
данные из удаленного источника и представляет в привычном для нас
виде - в виде таблицы, с которой нам, программистам, уже можно делать
все, что пожелаем. Комментарии объясняют для чего нам нужны
основные операторы. Нами применены некоторые новые конструкции, но
думаю, что они у Вас не вызовут проблем с пониманием. Если же нет, то
пишите - поговорим о них более подробно .
В результате работы программы получим следующую "картинку":
Обращу Ваше внимание только на одну деталь: все символьные поля
имеют тип Memo. Это связано с тем, что в ASP.NET WS надо как и в SOAP
указывать обязательность передачи схемы данных внутри XML файла.
Нами этого не было сделано, и FoxPro по умолчанию поставил
для строковой переменной максимально возможную длину. Решить эту
проблему можно двумя путями: попросить разработчиков Web Servise
включать схему в XML файл, или если это невозможно (что, как правило,
бывает в нашей жизни), то поработать немного самим и изменить
структуру полученной таблицы в месте приема. Есть еще вариант с
внешним XSD файлом, но я предлагаю опустить его, чтобы не усложнять
нашу и без того сложную жизнь.
Под влиянием общественности родился еще один пример, который может
даже носить некоторый практический характер - получение курсов
валют, публекуемых Центральным Банком России. Пример очень похож
на предыдущий, так что копируйте и запускайте:
***********
* пример получения курса валют ЦБ РФ с сайта http://web.cbr.ru/
***********
SET TALK OFF
* создаем объект на основе пакета SOAP 3.0
o=CREATEOBJECT("MSSoap.SoapClient30")
* соединяемся с удаленным источником данных
* в качестве параметра стандартного метода MSSoapInit задается URL
o.MSSoapInit("http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?WSDL")
* вызываем функцию данного Web Service как самую обычную функцию FoxPro
* Данная функция запрашивет курсы валют как DataSet
*o.DailyInfo()
loexception=NULL && устанавливаем объект, в котором будем отлавливать ошибки
tt=''
* применяем конструкцию TRY..CATCH чтобы возможные проблемы не повлияли на наше
* приложение. В принципе все можно было "упрятать" в эту конструкцию
TRY
tt=o.GetCursOnDate(DATE()) && параметр - текущая дата date()
CATCH TO loexception && если будет ошибка, то программа нам ее распечатает
? loexception.MESSAGE
? loexception.ERRORNO
ENDTRY
*? tt && если убрать здесь комментарий то на экране Вы увидите в случае успеха тип (Object)
IF ISNULL(loexception) && если не было ошибок, то продолжим
lcXML=tt.ITEM(0).parentnode.XML &&
* ? lcXML && если убрать здесь комментарий то на экране Вы увидите принятый Вами XML файл
* STRTOFILE(lcXML,'aaa.xml') && можно записать XML в файл на диск
* для дальнейшей работы создаем XML Adapter
LOCAL oXA AS XMLADAPTER
oXA = CREATEOBJECT("XMLAdapter")
* загружаем полученную информацию в созданный адаптер
oXA.LOADXML(lcXML,.F.,.T.)
IF USED('CURS')
USE IN CURS
ENDIF
* создаем таблицу из адаптера с названием CURS
oXA.TABLES[1].TOCURSOR(.F.,"CURS")
* немного поработаем над полученной таблицей для красоты
* к сожалению в 8 версии неправильно работает команда ALTER TABLE
* по этому применим дополнительный SELECT
IF VERSION(5)=800
SELECT SPACE(30) AS VNAME, 0000.0 AS VNOM ,000.0000 AS VCURS , SPACE(3) AS VCHCODE ;
FROM CURS WHERE 2=1 ;
INTO CURSOR NEWCURS NOFILTER ;
UNION ALL ;
SELECT MLINE(VNAME,1), VNOM,VCURS , MLINE(VCHCODE,1) ;
FROM CURS
ELSE
ALTER TABLE CURS ALTER COLUMN VNAME C(30)
ALTER TABLE CURS ALTER COLUMN VCHCODE C(3)
ALTER TABLE CURS ALTER COLUMN VNOM N(5,2)
ALTER TABLE CURS ALTER COLUMN VCURS N(8,4)
ENDIF
BROWSE && смотрим, что получили
ENDIF
* как обычно чистим за собой
RELEASE o
CLOSE DATABASES
example03.prg
Результат работы показан на нижеследующем рисунке (не плохой
результат за пару минут работы):
Думаю, что для первой части достаточно. Мы ждем Ваших откликов и
замечаний - что-то изменить, что-то добавить или, может, описать
возможные проблемы...
Статья: Web Services и MS Visual FoxPro Часть 2
В этой статье :
 Новые методы разработки приложений.
 Проблема.
 Дизайн. Создание базы данных.
Новые методы разработки приложений.
Приступим к созданию более сложного примера, сделав который Вы
получите необходимые навыки для применения технологии в
повседневной жизни и не только. Мы постараемся рассказать Вам, как
рекомендует Microsoft создавать приложения с нуля, условно разбив этот
путь на три основных части:
 1) Идентифицировать проблему (или поставить задачи и найти ответы
на вопросы: “ Что мы хотим сделать и что должно в итоге получиться?”,
“Какие ограничения или другие условия влияют на проект?”)
 2) Сделать основной дизайн (решить какие функции и возможности мы
должны будем разработать в системе, чтобы решить стоящую перед нами
задачу). Понять в общем как система будет работать, не вдаваясь в
излишние детали.
 3) Создать наше рабочее приложение (написать код и выполнить
другие виды работ, которые позволят сделать наш проект рабочим).
Кроме того, в основе нашего приложения будет лежать идея
многослойности (n-tier): слой базы данных, своеобразный слой бизнес –
логики (эту роль будет играть Service) и презентационный слой
(собственно клиентская часть – то что увидят наши клиенты). Идея,
лежащая в основе многослойных приложений, проста - каждый слой
можно разрабатывать и модернизировать в будущем по отдельности. Из
набивших уже "оскомину" на ум сразу приходит пример - заменить базу
данных FoxPro на базу данных MS SQL Server... Это, наверное, самый
популярный пример, так как зарплата программистов со знанием этих
SQL серверов почему-то намного выше нашей, хотя работать с
последним намного легче, чем с родными базами FoxPro. Странно все
это, куда катится наш мир?
К недостаткам данного подхода необходимо отнести его затратность:
чтобы внести изменения в программу, надо их внести в несколько мест.
Так, например, для этого сайта (да, этот сайт разработан как обычная
программа ASP.NET и подхода n-tire) www.sergey.co.uk если я захочу
изменить, например, форум , я должен буду внести измения в базу
данных, хранимые процедуры, слой работы с данными, бизнес-слой и ,
наконец, в презентационный слой. Что и говорить, кошмары
современного метода разработки приложений, или добро пожаловать в
мир, где все борются с безработицей подобным странным образом.
Проблема.
Главная и основная задача - создать приложение, которое бы послужило
базой для дальнейшей самостоятельной работы программиста FoxPro.
Отсюда вытекают остальные проблемы:
 создать систему обмена сообщений внутри фирмы
 возможность ведения частных и приватных диалогов
 наличие специальных администраторов
 совмещенная экранная форма в приложении – для администратора и
клиента (у администратора добавляются по мере необходимости
дополнительные объекты управления)
 стандартный набор функций клиента:
1. регистрация (для простоты пароли будем хранить в открытом виде)
2. публиковать свои сообщения: всем (ALL) или определенному лицу
(выбор из списка – в качестве параметра передовать на сервер
начальные буквы User_nick)
3. отвечать на общие и приватные сообщения
4. вся необходимая информация о других клиентах будет
предоставлена в Grid
 стандартный набор функций администратора:
1.
2.
3.
4.
добавлять пользователей и изменять их профайлы
удалять и редактировать любые сообщения
блокировать пользователей
наличие на форме специального фильтра, ограничивающего
период запрашиваемых с сервера сообщений
Дизайн. Создание базы данных.
В последнее время очень модно стало для разработки моделей баз
данных использование всевозможных средств визуального
моделирования даже на основе языка UML. Ваш покорный слуга написал
на эту тему диссертацию, и чтоб не провалить ее окончательно
присоединился к группе товарищей, которые предложили специальное
расширение UML, чтобы с его помощью можно было создавать
графические модели баз данных.
В этом направлении все сильно запущено, но все-таки воспользуемся
привычным для западного человека графическим изображением модели
базы данных, применив для этого ERM диаграмму и условно-бесплатный
редактор QSEE Superlight. В общем-то ничего сложного для своего
примера мы брать и не собирались:
Как мы видим из приведенной диаграммы, у нас будет три сущности
(entity), связанные соотношениями (relationships) один ко многим.
Имена атрибутов и их тип видны на рисунке. Далее, как любят
показывать в рекламе, нажимается кнопка и генерируется код создания
базы данных. Но не все так просто - этот код не всегда именно то, что
нам надо, и приходится, как правило, довольно много потом исправлять
руками. Да и наша мысль не стоит на месте. Изменения легче внести
руками в готовую базу, чем рисовать это в ERM, а потом снова
генерировать, исправлять... Короче, замкнутый круг на любителя.
Для меня, как практика, эти диаграммы являются хорошим подспорьем
для начальных бесед с клиентом. Обилие непонятных слов и картинки,
как правило, производят на него неизгладимые впечатления,
парализуют его мозг и позволяют легче выбивать из него деньги (может
поэтому специалисты Oracle, DB2 "гребут деньги лопатой" за гораздо
меньший труд, чем программисты FoxPro, что умеют "замутить" подобным
образом мозги заказчику).
Для простоты повторения мы рекомендуем Вам создать аналогичную
структуру директориев, как и в нашем проекте. Для серверной части,
например: C:\WS_MESSAGE\SERVER\
По причинам указанным выше (то есть как это принято у "серьезных
админов" баз данных) приведу создание таблиц нашей базы данных в
коде, запустив который Вы создадите требуемую базу данных и
заполните ее некоторыми начальными значениями. Код прозрачный, так
что копируете и вставляете в окно редактора программ FoxPro:
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0 (так как применим новые данные совместимые с SQL Server)
* Program-ID......: DB_CREATE.PRG
* Purpose.........: Создание базы данных для проекта обмена сообщениями внутри
*
компании. Данная база данных должна находится в дирректории
*
где Ваш Web Service (это сделано только для простоты
*
понимания данного проекта)
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 22/05/2005
* Last edited.....: 07/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
SET SAFETY OFF
CLOSE DATABASES ALL
PUBLIC m.pcpath
m.pcpath='C:\WS_MESSAGE\SERVER\'
* Так-как у нас SET SAFETY OFF
* предыдущая база будет удалена без предупреждения
IF FILE(m.pcpath+'\DBWS.DBC')
DELETE DATABASE (m.pcpath+'DBWS') DELETETABLES
ENDIF
* собственно создаем базу данных
CREATE DATABASE (m.pcpath+'DBWS')
CREATE TABLE m.pcpath+'USERS' CODEPAGE=1251( ;
User_ID INTEGER NOT NULL AUTOINC NEXTVALUE 1 STEP 1 PRIMARY KEY, ;
User_nick CHARACTER(10) NOT NULL, ;
PASSWORD CHARACTER(10) NOT NULL, ;
email VARCHAR(40), ;
city VARCHAR(30), ;
admin NUMERIC(1,0), ;
isblocked NUMERIC(1,0), ;
register T NOT NULL, ;
lastvisit T ;
)
* В нижеследующей таблице устанавливаем постоянные соотношения
* один ко многим с таблицей User
* хотя, в принципе, они нам и не нужны
CREATE TABLE m.pcpath+'Mes_Header' CODEPAGE=1251( ;
Mes_ID INTEGER NOT NULL AUTOINC NEXTVALUE 1 STEP 1 PRIMARY KEY , ;
User_from INTEGER NOT NULL REFERENCES USERS TAG User_ID, ;
User_to INTEGER NOT NULL REFERENCES USERS TAG User_ID, ;
WASUPDATED T NOT NULL, ;
TITLE VARCHAR(100), ;
published T NOT NULL ;
)
INDEX on TTOD(WASUPDATED) TAG WASUPDATED ADDITIVE
* здесь устанавливаем постоянное соотношение
* один ко многим с таблицей Mes_Header
* применение типа Blob для Message устраняет много проблем в будущем
* при передаче данных
CREATE TABLE m.pcpath+'Mes_body' CODEPAGE=1251( ;
Reply_ID INTEGER NOT NULL AUTOINC NEXTVALUE 1 STEP 1 PRIMARY KEY, ;
Mes_ID INTEGER NOT NULL REFERENCES Mes_Header TAG Mes_ID, ;
User_from INTEGER NOT NULL REFERENCES USERS TAG User_ID, ;
MESSAGE W, ;
published T ;
)
* добавим сразу несколько необходимых записей
* Пользователь ALL (или вроде как код для широковещательного сообщения
* очень важно ввести его первой записью, чтобы код был равен 1)
* пароль выбираем случайным образом, чтобы никто не писал от его имени
INSERT INTO (m.pcpath+'USERS') (User_nick, PASSWORD,admin ,register , lastvisit ) ;
VALUES;
('ALL',SYS(2015),0,DATETIME(),DATETIME())
* администратора (для реальной системы рекомендуется сменить пароль)
INSERT INTO (m.pcpath+'USERS') (User_nick, PASSWORD,admin ,register , lastvisit ) ;
VALUES;
('admin','admin',1,DATETIME(),DATETIME())
* добавляем просто пользователя для тестирования
* здесь можете добавить себя
INSERT INTO (m.pcpath+'USERS') (User_nick, PASSWORD,admin ,register , lastvisit ) ;
VALUES;
('sergey','sergey',1,DATETIME(),DATETIME())
* теперь добавим тестовое сообщение
INSERT INTO (m.pcpath+'Mes_Header') (User_from , User_to, WASUPDATED, TITLE, published) ;
VALUES ;
(2,1,DATETIME(),'Добро пожаловать в систему обмена сообщениями', DATETIME())
INSERT INTO (m.pcpath+'Mes_body') (Mes_ID,User_from ,MESSAGE,published ) ;
VALUES ;
(1,2,'Собственно поздравление.'+CHR(13)+'Надеемся, что все будет работать.', DATETIME())
* покажем, что у нас получилось
*DISPLAY TABLES
*DISPLAY DATABASE
* чистим за собой
CLOSE DATABASES
DB_CREATE.PRG
Как мы видим, ничего сложного. Только пара нюансов - использование
явного указания кодовой страницы и применение новго типа данных
Blob. К сожалению, они доступны только начиная с версии VFP 9.0.
Далее создаем хранимые процедуры - "рабочие лошадки" нашего
приложения. Существует мнение, что работать с базами данных следует
только через хранимые процедуры. В принципе, хороший похдод, тем
более, что все изменения в структуре Ваших данных и даже смену СУБД
можно производить без смены клиента. Еще одно удобство - это
ориентированность на наше WEB приложение (простота и прозрачность):
клиент послал запрос в виде параметров хранимой процедуры, а в ответ
получил результат. Все просто и надежно. Думаю, начав подобным
образом разрабатывать приложения, Вы выбьете почву из под
противников FoxPro, ибо они используют аналогичный подход и очень
этим гордятся.
Все хранимые процедуры начинаются с SP (это перевод на английский
ХП - Stored Procedures). Далее в названии используется английское
наименование функции (для больших задач рекомендуется после SP
использовать имя модуля, а затем название выполняемой функции).
Теперь небольшое лирическое отступление об использовании
английского языка. В принципе, можно использовать и русский, но
столкнувшись с некоторыми проблемами совместимости, я пришел к
выводу - использовать все-таки английский. Это хорошая практика, так
как неизвестно, куда Вас закинет судьба работать завтра - может в ОАЭ,
где никто не говорит по-русски, но смогут понять Ваш код на
английском.
Приведу текст всех Хранимых Процедур:
*/--------------------------------------------------------------------------/*
* Текст всех ХП находится в sp_procedures.txt
* Для создания всех ХП из текстового файла запустите Create_SP.PRG
*/--------------------------------------------------------------------------/*
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_chk_admin.PRG
* Purpose.........: Проверка на наличие прав администратора по user_id
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 22/05/2005
* Last edited.....: 22/05/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_chk_admin
PARAMETERS m.lcuser_id
SET DELETED ON
PRIVATE m.lnreturn_parameter
m.lnreturn_parameter=-1
IF m.lcuser_id>0
SELECT admin FROM USERS WHERE User_ID=m.lcuser_id INTO CURSOR user_exists
IF user_exists.admin >0
m.lnreturn_parameter=user_exists.admin
ENDIF
ENDIF
IF USED('USERS')
USE IN USERS
ENDIF
IF USED('USER_EXISTS')
USE IN user_exists
ENDIF
RETURN m.lnreturn_parameter
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_message_read.PRG
* Purpose.........: Чтение заголовков сообщений с сервера
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 22/05/2005
* Last edited.....: 22/05/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_message_read
PARAMETERS m.lddate, m.lcUser_nick, m.lcpassword, m.lndays_show
SET DELETED ON
PRIVATE m.lnuser_id, m.lnadmin, lcXML
STORE 0 TO m.lnadmin, m.lnuser_id
m.lnuser_id=sp_user_login_id(m.lcUser_nick, m.lcpassword)
lcXML=-1 && в случае неуспеха ответ в числовом виде
IF m.lnuser_id>0
m.lnadmin=sp_chk_admin(m.lnuser_id)
IF m.lnadmin>0 && это администратор, значит есть доступ ко всем сообщениям
SELECT Mes_Header.*,User_nick, city FROM Mes_Header ;
LEFT OUTER JOIN USERS ON Mes_Header.User_from=USERS.User_ID ;
WHERE TTOD(Mes_Header.WASUPDATED) >= ;
DATE(YEAR(m.lddate),MONTH(m.lddate),DAY(m.lddate)) - m.lndays_show ;
INTO CURSOR CUR_TEMP &&
ELSE && обычный пользователь
SELECT Mes_Header.*,User_nick, city FROM Mes_Header ;
LEFT OUTER JOIN USERS ON Mes_Header.User_from=USERS.User_ID ;
WHERE (Mes_Header.User_to=1 OR Mes_Header.User_to=m.lnuser_id or ;
Mes_Header.User_from=m.lnuser_id) AND ;
TTOD(Mes_Header.WASUPDATED) >= ;
DATE(YEAR(m.lddate),MONTH(m.lddate),DAY(m.lddate)) - m.lndays_show ;
INTO CURSOR CUR_TEMP &&
ENDIF
SELECT CUR_TEMP
IF RECCOUNT()>0
CURSORTOXML("CUR_TEMP","lcXML",1,1,0,"1")
ENDIF
ENDIF
CLOSE TABLES ALL
RETURN lcXML
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_user_login_xml.PRG
* Purpose.........: Проверка пароля и имени пользователя
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 22/05/2005
* Last edited.....: 06/05/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_user_login_xml
PARAMETERS m.User_nick, m.PASSWORD
SET DELETED ON
PRIVATE m.lcXML
m.lcXML=-1 && в случае неуспеха ответ в числовом виде
IF LEN(m.User_nick)>=3 AND LEN(m.PASSWORD)>3
SELECT User_ID, User_nick, admin, ISBLOCKED FROM USERS WHERE ;
UPPER(USERS.User_nick)==UPPER(m.User_nick) ;
AND UPPER(USERS.PASSWORD)==UPPER(m.PASSWORD) INTO CURSOR user_exists
IF RECCOUNT('USER_EXISTS')>0 AND EMPTY(user_exists.ISBLOCKED)
* у нас есть такой пользователь
CURSORTOXML('user_exists',"lcXML",1,1,0,"1")
ENDIF
ENDIF
IF USED('USER_EXISTS')
USE IN user_exists
ENDIF
IF USED('USERS')
USE IN USERS
ENDIF
RETURN m.lcXML
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_user_login_id.PRG
* Purpose.........: Проверка пароля и имени пользователя
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 22/05/2005
* Last edited.....: 06/05/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_user_login_id
PARAMETERS m.User_nick, m.PASSWORD
SET DELETED ON
PRIVATE m.lnreturn_parameter
m.lnreturn_parameter=-1 && в случае неуспеха ответ в числовом виде
IF LEN(m.User_nick)>=3 AND LEN(m.PASSWORD)>3
SELECT User_ID, User_nick, admin, ISBLOCKED FROM USERS WHERE ;
UPPER(USERS.User_nick)==UPPER(m.User_nick) ;
AND UPPER(USERS.PASSWORD)==UPPER(m.PASSWORD) INTO CURSOR user_exists
IF RECCOUNT('USER_EXISTS')>0 AND EMPTY(user_exists.ISBLOCKED)
* у нас есть такой пользователь
m.lnreturn_parameter=user_exists.User_ID
ENDIF
ENDIF
IF USED('USER_EXISTS')
USE IN user_exists
ENDIF
IF USED('USERS')
USE IN USERS
ENDIF
RETURN m.lnreturn_parameter
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_users_read_xml.PRG
* Purpose.........: Чтение списка пользователей с сервера
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 31/05/2005
* Last edited.....: 31/05/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_users_read_xml
PARAMETERS m.lcUser_nick, m.lcpassword
SET DELETED ON
PRIVATE m.lnuser_id, lcXML
STORE 0 TO m.lnuser_id
m.lnuser_id=sp_user_login_id(m.lcUser_nick, m.lcpassword)
lcXML=-1 && в случае неуспеха ответ в числовом виде
IF m.lnuser_id>0
SELECT User_ID, User_nick FROM USERS ;
INTO CURSOR CUR_TEMP
IF RECCOUNT('CUR_TEMP')>0
CURSORTOXML("CUR_TEMP","lcXML",1,1,0,"1")
ENDIF
ENDIF
CLOSE TABLES ALL
RETURN lcXML
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_new_message_add.PRG
* Purpose.........: Добавление нового сообщения в таблицы Mes_Header, Mes_body
*
а так-же обновляем информацию у клиента
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 30/05/2005
* Last edited.....: 04/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_new_message_add
PARAMETERS m.User_nick, m.PASSWORD , m.lnUser_to, m.lcTITLE, m.lcMes_Body
SET DELETED ON
SET MULTILOCKS ON
PRIVATE m.lnreturn_parameter, m.lnuser_id, m.lnmes_id
m.lnreturn_parameter=-1
m.lnuser_id=sp_user_login_id(m.User_nick, m.PASSWORD)
IF m.lnuser_id>0
BEGIN TRANSACTION
TRY
INSERT INTO Mes_Header (User_from , User_to, WASUPDATED, TITLE, published) ;
VALUES ;
(m.lnuser_id,m.lnUser_to,DATETIME(),ALLTRIM(m.lcTITLE), DATETIME())
m.lnmes_id=GETAUTOINCVALUE(0)
INSERT INTO Mes_body (Mes_ID,User_from,MESSAGE,published ) ;
VALUES ;
(m.lnmes_id,m.lnuser_id, m.lcMes_Body, DATETIME())
UPDATE USERS SET lastvisit = DATETIME() WHERE User_ID=m.lnuser_id
END TRANSACTION
m.lnreturn_parameter=1
CATCH TO oException
ROLLBACK
FINALLY
ENDTRY
ENDIF
CLOSE TABLES ALL
RETURN m.lnreturn_parameter
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_thread_read.PRG
* Purpose.........: Чтение всего потока одного сообщения с сервера
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 04/06/2005
* Last edited.....: 06/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_thread_read
PARAMETERS m.lcUser_nick, m.lcpassword, m.lnmes_id
SET DELETED ON
PRIVATE m.lnuser_id, m.lnadmin, lcXML
STORE 0 TO m.lnadmin, m.lnuser_id
m.lnuser_id=sp_user_login_id(m.lcUser_nick, m.lcpassword)
lcXML=-1 && в случае неуспеха ответ в числовом виде
IF m.lnuser_id>0
SELECT Mes_body.*,User_nick, city FROM Mes_body ;
LEFT OUTER JOIN USERS ON Mes_body.User_from=USERS.User_ID ;
WHERE Mes_ID=m.lnmes_id ;
INTO CURSOR CUR_TEMP &&
SELECT CUR_TEMP
IF RECCOUNT()>0
CURSORTOXML("CUR_TEMP","lcXML",1,1,0,"1")
ENDIF
ENDIF
CLOSE TABLES ALL
RETURN lcXML
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_existed_message_add.PRG
* Purpose.........: Добавление нового сообщения в таблицу Mes_body
*
а так-же обновляем информацию у клиента
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 05/05/2005
* Last edited.....: 06/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_existed_message_add
PARAMETERS m.User_nick, m.PASSWORD , m.lcMes_Body, m.lnmes_id
SET DELETED ON
SET MULTILOCKS ON
PRIVATE m.lnreturn_parameter, m.lnuser_id
m.lnreturn_parameter=-1
m.lnuser_id=sp_user_login_id(m.User_nick, m.PASSWORD)
IF m.lnuser_id>0
BEGIN TRANSACTION
TRY
INSERT INTO Mes_body (Mes_ID,User_from,MESSAGE,published ) ;
VALUES ;
(m.lnmes_id,m.lnuser_id, m.lcMes_Body, DATETIME())
UPDATE USERS SET lastvisit = DATETIME() WHERE User_ID=m.lnuser_id
END TRANSACTION
m.lnreturn_parameter=1
CATCH TO oException
ROLLBACK
FINALLY
ENDTRY
ENDIF
CLOSE TABLES ALL
RETURN m.lnreturn_parameter
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_message_delete.PRG
* Purpose.........: Удаление всего потока сообщения с сервера
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 05/06/2005
* Last edited.....: 05/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_message_delete
PARAMETERS m.lcUser_nick, m.lcpassword, m.lnmes_id
SET DELETED ON
SET MULTILOCKS ON
PRIVATE m.lnuser_id, m.lnadmin, lcXML
STORE 0 TO m.lnadmin, m.lnuser_id
m.lnuser_id=sp_user_login_id(m.lcUser_nick, m.lcpassword)
m.lnadmin=sp_chk_admin(m.lnuser_id)
m.lnreturn_parameter=-1 && в случае неуспеха ответ в числовом виде
IF m.lnuser_id>0 AND m.lnadmin>0
BEGIN TRANSACTION
TRY
DELETE FROM Mes_Header WHERE Mes_ID=m.lnmes_id
DELETE FROM Mes_body WHERE Mes_ID=m.lnmes_id
UPDATE USERS SET lastvisit = DATETIME() WHERE User_ID=m.lnuser_id
END TRANSACTION
m.lnreturn_parameter=1
CATCH TO oException
ROLLBACK
FINALLY
ENDTRY
ENDIF
CLOSE TABLES ALL
RETURN m.lnreturn_parameter
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_answer_delete.PRG
* Purpose.........: Удаление одного ответа из сообщения с сервера
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 05/06/2005
* Last edited.....: 05/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_answer_delete
PARAMETERS m.lcUser_nick, m.lcpassword, m.lnReply_id, m.lnmes_id
SET DELETED ON
SET MULTILOCKS ON
PRIVATE m.lnuser_id, m.lnadmin, lcXML
STORE 0 TO m.lnadmin, m.lnuser_id
m.lnuser_id=sp_user_login_id(m.lcUser_nick, m.lcpassword)
m.lnadmin=sp_chk_admin(m.lnuser_id)
m.lnreturn_parameter=-1 && в случае неуспеха ответ в числовом виде
IF m.lnuser_id>0 AND m.lnadmin>0
BEGIN TRANSACTION
TRY
DELETE FROM Mes_body WHERE Reply_ID=m.lnReply_id
*
теперь смотрим - остались ли еще сообщения и если нет - то удаляем
*
и заголовок сообщения
SELECT COUNT(Mes_ID) AS RECORDS ;
FROM Mes_body ;
WHERE Mes_ID=m.lnmes_id ;
INTO CURSOR CUR_TEMP
SELECT CUR_TEMP
IF CUR_TEMP.RECORDS=0
DELETE FROM Mes_Header WHERE Mes_ID=m.lnmes_id
ENDIF
UPDATE USERS SET lastvisit = DATETIME() WHERE User_ID=m.lnuser_id
END TRANSACTION
m.lnreturn_parameter=1
CATCH TO oException
ROLLBACK
FINALLY
ENDTRY
ENDIF
CLOSE TABLES ALL
RETURN m.lnreturn_parameter
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_users_profile_read_xml.PRG
* Purpose.........: Чтение профиля пользователя с сервера
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 06/06/2005
* Last edited.....: 06/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_users_profile_read_xml
PARAMETERS m.lcUser_nick, m.lcpassword, m.lnUserProfile_ID
SET DELETED ON
PRIVATE m.lnuser_id, lcXML, m.lnadmin
STORE 0 TO m.lnuser_id
m.lnuser_id=sp_user_login_id(m.lcUser_nick, m.lcpassword)
m.lnadmin=sp_chk_admin(m.lnuser_id)
lcXML=-1 && в случае неуспеха ответ в числовом виде
IF m.lnuser_id>0 AND m.lnadmin>0
SELECT * FROM USERS ;
INTO CURSOR CUR_TEMP WHERE User_ID=m.lnUserProfile_ID READWRITE NOFILTER
IF RECCOUNT('CUR_TEMP')=0
SELECT CUR_TEMP
APPEND BLANK
ENDIF
CURSORTOXML("CUR_TEMP","lcXML",1,1,0,"1")
ENDIF
CLOSE TABLES ALL
RETURN lcXML
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_users_add_edit.PRG
* Purpose.........: Добавление/изменение профиля клиента
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 06/06/2005
* Last edited.....: 06/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_users_add_edit
PARAMETERS m.lcUser_nick, m.lcpassword, m.lnUserProfile_ID, m.lcXML_Cursor
SET DELETED ON
SET MULTILOCKS ON
PRIVATE m.lnuser_id, m.lnadmin, lcXML
STORE 0 TO m.lnadmin, m.lnuser_id
m.lnuser_id=sp_user_login_id(m.lcUser_nick, m.lcpassword)
m.lnadmin=sp_chk_admin(m.lnuser_id)
m.lnreturn_parameter=-1 && в случае неуспеха ответ в числовом виде
IF m.lnuser_id>0 AND m.lnadmin>0 AND !EMPTY(m.lcXML_Cursor)
XMLTOCURSOR(m.lcXML_Cursor,'TMCURSOR',4)
SELECT TMCURSOR
IF RECCOUNT()>0 && у нас есть работа
IF m.lnUserProfile_ID>0 && обновление существующих данных
BEGIN TRANSACTION
TRY
* это конечно узкое место, но нам хотелось все сделать с помощью CRUD команд
UPDATE USERS SET User_nick=TMCURSOR.User_nick, PASSWORD = TMCURSOR.PASSWORD,;
email=TMCURSOR.email, city=TMCURSOR.city,admin = TMCURSOR.admin, ;
ISBLOCKED=TMCURSOR.ISBLOCKED ;
WHERE User_ID=m.lnUserProfile_ID
END TRANSACTION
m.lnreturn_parameter=1
CATCH TO oException
ROLLBACK
FINALLY
ENDTRY
ELSE && добавление нового клиента
BEGIN TRANSACTION
TRY
INSERT INTO USERS ;
(User_nick,
admin
PASSWORD ,
email,
, register, lastvisit, ISBLOCKED
city,
;
);
VALUES ;
(TMCURSOR.User_nick,TMCURSOR.PASSWORD,TMCURSOR.email,TMCURSOR.city,;
TMCURSOR.admin, DATETIME(), DATETIME(),TMCURSOR.ISBLOCKED)
END TRANSACTION
m.lnreturn_parameter=1
CATCH TO oException
ROLLBACK
FINALLY
ENDTRY
ENDIF
ELSE && нечего делать
ENDIF
ENDIF
CLOSE TABLES ALL
RETURN m.lnreturn_parameter
Stored Procedures DBWS.DBC
Теперь, собственно, сам текст этой небольшой процедуры, которая
создает эти ХП:
*/-----------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: Create_SP.PRG
* Purpose.........: Создание Хранимых Процедур для базы данных DBWS.DBC
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 22/05/2005
* Last edited.....: 05/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PUBLIC m.pcpath
m.pcpath='C:\WS_MESSAGE\SERVER\'
OPEN DATABASE (m.pcpath+'DBWS.DBC')
APPEND PROCEDURES FROM (m.pcpath+'SP_PROCEDURES.TXT') as 1251 OVERWRITE
CLOSE DATABASES
Create_SP.PRG
Кстати, в проекте, который Вы можете скачать ЗДЕСЬ (файл
ws_mes_serv.zip 34 Kb) , так и сделано.
Второй путь - открыть базу данных, написать команду MODIFY
PROCEDURE и в открывшееся окно скопировать текст всех хранимых
процедур.
При отладке ХП могут иногда возникнуть проблемы - не отображаются
внесенные Вами изменения. Для этого Вам надо открыть базу данных в
режиме EXCLUSIVE и выполнить команду PACK DATABASE.
Теперь солидная "ложка дегтя" к нашей "бочке меда". Дело в том, что
ряд разработчиков используют базы данных FoxPro и для других
приложений, например, в ASP.NET. Работа с базами данных
осуществляется с помощью Visual FoxPro OLE DB Provider, который, к
сожалению, пока все еще не поддерживает структуру
TRY..CATCH..ENDTRY. В этом случае нам придется обойтись старыми
приемами, основанными на буферизации таблиц и вложенных
транзакциях. Для простоты повествования приведем текст примера
только для одной хранимой процедуры sp_message_delete():
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: sp_message_delete.PRG
* Purpose.........: Удаление всего потока сообщения с сервера
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 05/06/2005
* Last edited.....: 05/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
PROCEDURE sp_message_delete
PARAMETERS m.lcUser_nick, m.lcpassword, m.lnmes_id
SET DELETED ON
SET MULTILOCKS ON
PRIVATE m.lnuser_id, m.lnadmin, lcXML
STORE 0 TO m.lnadmin, m.lnuser_id
m.lnuser_id=sp_user_login_id(m.lcUser_nick, m.lcpassword)
m.lnadmin=sp_chk_admin(m.lnuser_id)
m.lnreturn_parameter=-1 && в случае неуспеха ответ в числовом виде
CLOSE TABLES ALL
SELECT 0
USE MES_HEADER SHARED
=CURSORSETPROP("Buffering" ,5,'MES_HEADER' )
SELECT 0
USE MES_BODY SHARED
=CURSORSETPROP("Buffering" ,5,'MES_BODY' )
SELECT 0
USE USERS ALIAS USERS SHARED
=CURSORSETPROP("Buffering" ,5,'USERS' )
SELECT 0
IF m.lnuser_id>0 AND m.lnadmin>0
LOCAL llRollBack
llRollBack=.F.
BEGIN TRANSACTION
DELETE FROM MES_HEADER WHERE Mes_ID=m.lnmes_id
DELETE FROM MES_BODY WHERE Mes_ID=m.lnmes_id
UPDATE USERS SET lastvisit = DATETIME() WHERE User_ID=m.lnuser_id
IF !TABLEUPDATE(2,.T.,'MES_HEADER')
llRollBack=.T.
ELSE
IF !TABLEUPDATE(2,.T.,'MES_BODY')
llRollBack=.T.
ELSE
IF !TABLEUPDATE(2,.T.,'USERS')
llRollBack=.T.
ENDIF
ENDIF
ENDIF
IF llRollBack
ROLLBACK
ELSE
END TRANSACTION
m.lnreturn_parameter=1
ENDIF
ENDIF
CLOSE TABLES ALL
RETURN m.lnreturn_parameter
Вариант ХП sp_message_delete() для Visual FoxPro OLE DB
Provider
Код как обычно прозрачен, так что думаю, что можно опустить
объяснения. В скачиваемом файле (ws_mes_serv.zip 34 Kb) процедура
для создания Хранимых Процедур носит название
CREATE_SP_OLEDB.PRG, а их тексты находятся в файле
SP_PROCEDURES_OLEDB.TXT. Для примера с Web Service на основе
FoxPro не имеет значения какие хранимые процедуры использовать, но
Вам, как программисту FoxPro, должна быть понятна эта разница и
ограничения, накладываемые Visual FoxPro OLE DB Provider.
Статья: Web Services и MS Visual FoxPro Часть 3
В этой статье :
 Подробное создание прототипа WebService.
 Возможные проблемы.
Подробное создание прототипа Web Service.
Создадим прототип Web Service. Этот WS будет иметь то же название,
что и реальный, с одной лишь разницей - у него будет всего один метод
(процедура), котрый будет возвращать 1. Здесь мы подробно опишем,
как его создать, опубликовать и проверить его работу. Далее при
создании WS для нашего реального проекта мы опустим все эти
подробности. Мы надеемся, что такой подход упростит
понимание рассматриваемой технологии.
1. Создадим директорий на Вашем компьютере C:\WS_MESSAGE\SERVER
2. Создадим в этом директории программу с именем ws_server.prg ,
поместив туда следующий код:
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0 (так как применим новые данные совместимые с SQL Server)
* Program-ID......: WS_SERVER.PRG
* Purpose.........: Непосредственно WEB Service
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 23/05/2005
* Last edited.....: 23/05/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
DEFINE CLASS WS_MES_SERVER AS SESSION OLEPUBLIC
PROCEDURE INIT && процедура, выполняемая при вызове Web Service
* Вы можете писать сюда все что Вам необходимо для инициализации работы программы
* Например, глобальные переменные
SET DELETED ON
SET DATE DMY
SET EXCLUSIVE OFF
SET MULTILOCKS ON
SET REPROCESS TO 1000
SET STRICTDATE TO 0
*!* If you do any work with SQL Pass-Through
*!* these settings are a good idea.
SQLSETPROP(0,"DispLogin",3)
SQLSETPROP(0,"DispWarnings",.F.)
ENDPROC
* это и есть наш тестовый пример
PROCEDURE test() AS INTEGER
RETURN (1)
ENDPROC
ENDDEFINE
3. Cоздадим новый проект с названием ws_server и добавим туда
созданную ранее программу ws_server.prg. По умолчанию этот модуль
примет статус MAIN (то есть главный), что нам и надо:
4. Построим проект как multi-threaded COM server (DLL):
Вот мы и получили DLL, готовую к построению Web Service. К
сожалению, пока у нас нет возможности использовать для создания
нашего класса визуальные средства, так что приходится все писать
руками в коде. Замечу еще, что наш класс на базе session должен быть
определен как OLEPUBLIC.
Сразу проведем тестирование созданной DLL. Для этого в командном
окне наберем следующий код (набирается одна строка, затем она
выполняется, затем следующая и т.д.):
o=CREATEOBJECT('WS_SERVER.WS_MES_SERVER')
? o.test
RELEASE o
Если у Вас все правильно, то на экране получим результат работы
программы 1.
И, наконец, пришло время опубликовать (или преобразовать обычной
DLL в WS) наш первый Web Service. Для этого:
1. Нажмем правую клавишу мышки в окне проекта на имени нашей
программы:
2. Появится окно Wizard Selection, в котором следует выбрать Web
Services Publisher:
3. После этого может появиться предупреждающее сообщение с
короткими инструкциями, что надо делать (просто ответьте OK).
Следующим появится Web Services Publishing диалог:
4. Выбираем Advanced Button (на предыдущем рисунке эта
кнопка обведена красным). Появляется диалог для определения места,
где будет находиться создаваемый Web Service:
5. Для первого раза выберем место New, изменим File Output Path и New
Virtual Directory Name, как на следующем рисунке (и заодно выберем
пункт Set selected URL as default Web service location, чтобы при
последующих публикациях Web Service съэкономить наше время). Для
выполнения наших инструкций нажимаем кнопку Create:
6. После непродолжительной работы компьютера появляется экран в
котором мы еще раз все сможем поменять (выбираем ISAPI - вроде как
такой способ "прослушки" работает быстрее, чем ASP):
7. В принципе, все у нас выбрано правильно. Рекомендую только
заглянуть во вкладку Methods:
8. Дело в том, что позже, при создании новых методов в программе, они
не будут автоматически выбираться построителем. Сейчас у Вас выбран
единственный метод test. В будущем Вам надо будет делать это
самостоятельно через эту вкладку (здесь же можно будет отключать
неиспользуемые методы). Нажимаем кнопку OK. Возвращаемся в
начальное окно публикации WS. Теперь надо нажать кнопку Generate:
9. Если у Вас все прошло нормально, то получите примерно такое
системное сообщение:
Ну вот и все! Не смотря на обилие рисунков, система в общем-то простая
и прямолинейная. Сделав один раз, Вы сможете это повторять уже много
раз без подсказок.
Осталось проверить работу данного Web Service. Как и в примере с
проверкой COM DLL , наберите следующие команды в командном окне
VFP:
o=CREATEOBJECT("MSSoap.SoapClient30")
o.MSSoapInit("http://sergey04/ws_server/ws_mes_server.WSDL")
? o.test
release o
Если Вы получили на экране снова нашу любимую цифру 1 - это значит,
что первый созданный Вами Web Service (и опубликованный пока что в
Intranet) работает. Вот так незаметно для постороннего глаза Вы и
научились программировать в глобальном масштабе.
Думаю, что для этого раздела достаточно. Теперь попробуем описать что
делать, если что-то не работает.
Возможные проблемы.
Автор данной статьи имеет сравнительно небольшой опыт в решении
проблем (всего 5 лет), так как технология довольно молодая и в общемто неплохо спроектирована. Если сравнивать количество отказов с .NET
framework, то в последней было гораздо больше проблем и
беспричинного отказа, который решался только удалением данной
надстройки путем подачи команды aspnet_regiis.exe -ua , а затем полной
регистрации aspnet_regiis.exe -i , благо что MS предусмотрел все
заранее. С SOAP 3.0 данный фокус, увы, не проходит. Если что-то стало
неправильно работать, то простой переустановкой данного пакета
ничего не добьешся - надо чистить регистр (regedit команда в окне RUN).
Трудное это дело и, думаю, что до него у Вас не дойдет.
В данном разделе рассмотрим мелкие проблемы, которые могут
возникнуть при совместной работе SOAP и IIS. Картинки будут с IIS 5.1
(WIndows XP professional), но в IIS 5.0, который есть в Windows 2000,
они аналогичны. К сожалению, ничего не могу сказать про IIS 6.0
(Windows 2003), так как автору не удалось заставить в нем работать
пакет SOAP 3.0 (несмотря на личные заверения Rick Strahl и мою
полемику с ним в форуме UT, что все должно работать).
Итак, откроем Internet Information Services (выбрав свойства
виртуального директория, где находится наш Web Service). У меня
примерно вот такая картинка:
Построитель должен создать Application для нашего приложения. Если
этого не произошло, то это можно сделать "вручную" (вместо кнопки
Remove будет кнопка Create). Для решения глобальной проблемы с Web
Service - остутствие вызова по умолчанию обработчиков файлов с
расширением WSDL - откроем окно Configuration:
Если указанной строки нет, то ее надо добавить:
Причем для Executable Microsoft рекомендует внести строку:
"c:\progra~1\common~1\mssoap\Binaries\SOAPIS30.dll"
(Особенно это касается Windows 2000, который не понимает пробелов в
именах файлов и пути в этом окне ввода). Вот собственно и все. Теперь
должно работать.
Статья: Web Services и MS Visual FoxPro Часть 4
В этой статье :
 Web Service для нашего большого примера.
 Публикация Web Service (некоторые ключевые моменты).
Web Service для нашего большого примера.
Ну вот и мы и добрались до самого главного - Web Service. Не буду Вас
томить ожиданием, а приведу сразу весь код:
*/--------------------------------------------------------------------------/*
*
* MS VFP version..: 9.0 (так как применим новые данные совместимые с SQL Server)
* Program-ID......: WS_MES_SERVER.PRG
* Purpose.........: Непосредственно WEB Service
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 23/05/2005
* Last edited.....: 23/05/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
DEFINE CLASS WS_MES_SERVER AS SESSION OLEPUBLIC
PROCEDURE INIT && процедура, выполняемая при каждом вызове Web Service
* Вы можете писать сюда все что Вам необходимо для инициализации работы программы
* Например, глобальные переменные
SET DELETED ON
SET DATE DMY
SET EXCLUSIVE OFF
SET MULTILOCKS ON
SET REPROCESS TO 1000
SET STRICTDATE TO 0
SET CENTURY ON
* явно определяем установки среды, которые будут видны во всем Web Service
* обычно эту информацию следует записывать во внешний DBF файл конфигурации
PUBLIC gcWebSDat
gcWebSDat='C:\ws_message\server\'
*!* If you do any work with SQL Pass-Through
*!* these settings are a good idea.
SQLSETPROP(0,"DispLogin",3)
SQLSETPROP(0,"DispWarnings",.F.)
ENDPROC
*
* Процедура login в систему - принимает в качестве параметров login и password
* выдает курсор с одной строкой в виде информации о клиенте
*
PROCEDURE login(m.lclog,m.lcpsw) AS STRING
IF !DBUSED('DBWS')
OPEN DATABASE (gcWebSDat+'DBWS')
ENDIF
lcXMLa11=-1
lcXMLa11=sp_user_login_xml(m.lclog,m.lcpsw)
CLOSE DATABASES ALL
RETURN (lcXMLa11)
ENDPROC
*
* В каждой процедуре передается снова логин, пароль
* и дополнительные параметры. В реальной жизни я их шифрую,
* причем все без исключения.
*
* В принципе, систему безопасности можно и ослабить...
*
* Данный пример показывает, как можно упростить разработку
* Web Service при выполнении однотипных операций
* кроме того такая некоторая "запутанность" приводит к повышению
* уровня безопасности.
*
PROCEDURE message_read(m.lckey_word,m.lcUser_nick, m.lcpassword,m.lcparameter1,m.lcparameter2,;
m.lcparameter3,m.lcparameter4,m.lcparameter5,m.lcparameter6) AS STRING
IF !DBUSED('DBWS')
OPEN DATABASE (gcWebSDat+'DBWS')
ENDIF
lcXMLa11=-1
DO CASE
CASE m.lckey_word='READ_MESSAGES_START' && чтение заголовков сообщений
lcXMLa11=sp_message_read(m.lcparameter1, m.lcUser_nick, m.lcpassword, m.lcparameter2)
CASE m.lckey_word='READ_ALL_USERS' && чтение всех пользователей
lcXMLa11=sp_users_read_xml(m.lcUser_nick, m.lcpassword)
CASE m.lckey_word='ADD_NEW_MESSAGE' && добавление нового сообщения
lcXMLa11=sp_new_message_add(m.lcUser_nick,
m.lcpassword,m.lcparameter1,m.lcparameter2,m.lcparameter3)
CASE m.lckey_word='READ_THREAD_START' && чтение всех ответов на конретное сообшение
lcXMLa11=sp_thread_read(m.lcUser_nick, m.lcpassword,m.lcparameter1)
CASE m.lckey_word='ADD_EXISTS_MESSAGE' && добавление ответа на существующее сообщение
lcXMLa11=sp_existed_message_add(m.lcUser_nick, m.lcpassword,m.lcparameter1,m.lcparameter2)
CASE m.lckey_word='DELETE_THREAD' && Удаление всего потока сообщения с сервера
lcXMLa11=sp_message_delete(m.lcUser_nick, m.lcpassword,m.lcparameter1)
CASE m.lckey_word='DELETE_ANSWER' && Удаление одного ответа из сообщения с сервера
lcXMLa11=sp_answer_delete(m.lcUser_nick, m.lcpassword,m.lcparameter1,m.lcparameter2)
CASE m.lckey_word='READ_PROFILE_USERS' && Чтение профиля пользователя с сервера
lcXMLa11=sp_users_profile_read_xml(m.lcUser_nick, m.lcpassword,m.lcparameter1)
CASE m.lckey_word='ADD_NEW_USER' && Добавление/изменение профиля клиента
lcXMLa11=sp_users_add_edit(m.lcUser_nick, m.lcpassword,m.lcparameter1,m.lcparameter2)
ENDCASE
CLOSE DATABASES ALL
RETURN (lcXMLa11)
ENDPROC
* это и есть наш тестовый пример
PROCEDURE test() AS INTEGER
RETURN (1)
ENDPROC
ENDDEFINE
WS_MES_SERVER.PRG
Как Вы видите, ничего серьезного и таинственного. В процедуре INIT
можно задавать любые установки, которые будут потом видимы всем
методам внутри класса. Благодаря применению ХП в базе данных, здесь
все свелось просто к вызову соответствующих ХП с параметрами. Причем
параметр может быть XML файл, в котором можно спрятать довольно
большую таблицу или много других параметров (тем самым обходя
ограничения на количество переданных праметров). В практической
жизни таблицы я вначале сохраняю в виде текстового файла с
разделителями, затем этот файл вставляю в поле MEMO BINARY курсора,
преобразую получившуюся таблицу с одной строкой в XML курсор и
отправляю его по сети. Так на файлах размером более 5 Mb получается
существенная экономия интернет-трафика и, соответственно, повышение
скорости работы.
Процедуры могут вернуть только один параметр. В нашем случае это, как
правило, XML файл, в котором находится наш курсор. В принципе, все
довольно просто и прозрачно.
Публикация Web Service (некоторые ключевые моменты).
Мы очень надеемся,что Вы проделали предыдущий пример и научились
публиковать простейший WS. Мы не изменили имя файла для нашего
примера, так что основные моменты остаются прежними. Первое
отличие появится при построении Multi-Thread COM server (dll) компилятор выдаст ошибку, что не может найти некоторые файлы,
например:
Приглядевшись повнимательней, мы поймем, что компилятор не смог
найти Хранимую Процедуру. К сожалению, автору данной статьи не
удалось найти способа избежания появления данных сообщений об
ошибках, так что просто жмем кнопку Ignore и продолжаем работу. В
итоге "допустимы" следующие "ошибки":
То есть мы, по существу, получили полный список ХП нашей базы
данных. В принципе, как мне посказали на форуме - можно включить
нашу базу данных в проект, где находится наш WS и сообщения об
ошибках исчезнут. Я этого не делал, так что у Вас есть возможность
поэкспериментировать.
Теперь следующая проблема. Если после создания Web Service Вы
попытаетесь запустить его, он будет блокирован IIS (Internet Information
Service). Вы не сможете создать DLL (а если Вы обращались к Вашей
базе данных, то она тоже остается открытой, и Вы не сможете отбразить
внесенные изменения в ХП). Для решения данных проблем необходимо
перезапустить Ваш IIS. Например, как показано на следующей картинке:
Далее жмём OK
Если Вы нетерпеливы, то можно нажать кнопку немедленной
перезагрузки в следующем экране:
Приведенные снимки экранов актуальны для IIS 5.1 и ОС Windows XP.
Для других версий продуктов они могут отличаться в ту или иную
сторону. И не забудьте, что если Вы в сети, то сразу после загрузки ктото может снова затребовать Ваш ресурс, и Вам придется повторить
операцию с начала или просто остановить IIS и сделать свое дело.
Вернемся снова к нашему Web Service. Просто покажу финальный экран,
где будут видны все методы создаваемого нами WS:
Как мы видим, методов три. Мы специально оставили уже известный Вам
метод test. В будущем Вы сможете легко проверить, работает Ваш Web
Service или нет. И где проблема - в самом WS или в правах доступа к
таблицам базы данных.
Вот вроде бы и все. Web Service работает, но если мы попытаемся
работать с реальными данными, Вы увидите, что не сможете ничего
записать в Вашу базу данных. Причина тут простая - по умолчанию у WS
нет права писать на Вашем компьютере. Далее наши объяснения будут
сильно отличаться от того, где находится Ваш компьютер, и какая на нем
установалена операционная система. Начнем с Windows XP на отдельно
стоящей (не включенной в домен) рабочей станции. Находим папку, где
у Вас WS (В нашем случае C:\ws_message\server):
Нажимаем правую кнопку на папке server, из всплывающего меню
выбираем Property. На этой закладке выбираем ярлык Sharing, на
котором производим следующие установки (но для этого у Вас должны
быть права администратора на этот компьютер):
Вы уменьшаете уровень защищенности Вашего компьютера, так что
смотрите сами, чтобы кто-то с негативными намерениями не испортил
Вашу информацию.
Если Ваш компьютер находится в домене, то Вам необходимо дать доступ
на запись к директории, где у Вас находятся данные, клиенту
компьютера по имени IUSR_SERGEY04 (это операцинная система создает
этого клиента по умолчанию, где IUSR_ у всех компьютров постоянно, а
SERGEY04 - имя Вашей машины (у Вас оно должно быть другим)). Вы
можете сменить пароль этого клиента или даже дать доступ глобальному
интернет-клиенту в Вашем домене. Посмореть клиента по умолчанию для
Вашего IIS можно, нажав правую клавишу мышки на Вашем
виртуальном директории и выбрав в свойствах - Directory Security:
Если нажать клавишу Browse, то можно сменить клиента.
Вот в общем-то и все. Трудно объять необъятное, но думаю, что Ваши
вопросы помогут сформировать FAQ. Скачать исходные тексты для Web
Service и создания Базы данных можно отсюда (файл ws_mes_serv.zip 34
Kb).
Статья: Web Services и MS Visual FoxPro Часть 5
В этой статье :
 "Толстый клиент" для работы с Web Service.
"Толстый клиент" для работы с Web Service.
Вы скорее всего слышали что-то о "тонком" и "толстом клиенте". В первом
приближении тонкий (thin) клиент - это программа, с которой Вы можете
работать на "голой" машине, на которой установлена только ОС и Browser.
Можно ли для нашего Web Service создать такое приложение? Конечно, да!
И очень легко. Самый простой способ - написать его на Classic ASP или
ASP.NET. Но в этой статье мы рассмотрим толстый (fat) клиент. Данный
клиент имеет преимущество: на таком компьютере можно установить
библиотеку для запуска приложения FoxPro или даже полностью FoxPro,
если это машина разработчика.
Итак, начнем. Файл проекта поместим в директорий C:\ws_message\client\ .
Общая картина будет примерно такой:
В головном модуле задаем глобальные переменные, производим проверку
на недопустимость многократного запуска нашего приложения. В общем,
обычная несложная рутина:
*/-----------------------------------------------------------------------/*
*
* MS VFP version..: 9.0
* Program-ID......: MAIN.PRG
* Purpose.........: Главная программа клинета системы обмена сообщений
* Project Manager.:
* Programmer......: Sergey Chavlytko
* Start...........: 29/05/2005
* Last edited.....: 07/06/2005
*
* (С) www.sergey.co.uk 2005
*/--------------------------------------------------------------------------/*
IF _VFP.STARTMODE=0
_SCREEN.VISIBLE= .T.
* при запуске из среды разработки данная клавиша сохранит много Вашего времени
* если понадобится прервать выполнение программы.
ON KEY LABEL F12 SET SYSMENU TO DEFAULT
ELSE
_SCREEN.VISIBLE= .F.
ENDIF
* очищаем на всякий случай окружение
RELEASE ALL EXTENDED
CLEAR ALL
* делаем стандартные установки
SET CONFIRM
ON
SET DELETED
ON
SET EXACT
OFF
SET EXCLUSIVE OFF
SET MULTILOCKS ON
SET SAFETY
SET TALK
OFF
OFF
SET DATE BRITISH
SET HOURS TO 24
* запоминаем номер версии программы в глобальной переменной
PUBLIC m.gcproramversion
m.gcproramversion=''
AGETFILEVERSION(AVER,'mesclient.exe')
IF TYPE('AVER')#'U'
m.gcproramversion=' (ver: ' +AVER(11)+')'
ENDIF
PUBLIC m.gltransact
m.gltransact=.f.
* проверяем наличие предыдущей копии программы в памяти
IF AppAlreadyRunning()
=MESSAGEBOX("Вы конечно извините, но запустить данную программу "+ ;
"можно только один раз...", 0 + 16 +0, ;
"Ошибка в течении старта программы")
QUIT
ENDIF
* создаем переменные путей для создания временных таблиц и курсоров
* в принципе сейчас они не нужны, но это хорошая привычка все указывать
* явно и мы будем ей следовать...
PUBLIC m.gctmp
m.gctmp=SYS(2023)+'\'
PUBLIC m.gccurdir
m.gccurdir=SYS(5)+SYS(2003)+'\'
* явно определяем установки среды, которые будут видны во всей программе
* обычно эту информацию следует записывать во внешний DBF файл конфигурации
* если Вы все повторяете за нами все, то скорее всего Вам не надо будет менять
* имя Web Service (localhost - это только для Вашей локальной машины)
* если Вы будете использовать WS в Intranet - то замените на имя Вашей машины
* (например SERGEY04)
* при использовании в Internet - на реальный адрес...
*
PUBLIC gcwebserv
gcwebserv='http://localhost/ws_server/ws_mes_server.wsdl'
* объявляем глобальные переменные, которые потом будем использовать в программе
* мы будем использовать этот вариант для простоты, хотя в последнее время реко* мендуют создавать глобальный объект приложение и менять его свойства...
PUBLIC m.gclog, m.gcpsw, m.gcadmin,m.gnuser_id
STORE SPACE(10) TO m.gclog, m.gcpsw
m.gcadmin=0
m.gnuser_id=0
DO FORM frmmain
READ EVENTS
QUIT
* функция избежания повтороного запуска программы
FUNCTION AppAlreadyRunning
LOCAL hsem, lpszSemName
#DEFINE ERROR_ALREADY_EXISTS 183
DECLARE INTEGER GetLastError IN win32API
DECLARE INTEGER CreateSemaphore IN WIN32API ;
STRING @ lpSemaphoreAttributes, ;
LONG lInitialCount, ;
LONG lMaximumCount, ;
STRING @ lpName
lpszSemName = 'MESCLIENT'
hsem = CreateSemaphore(0,0,1,lpszSemName)
RETURN (hsem # 0 AND GetLastError() == ERROR_ALREADY_EXISTS)
*
* оформим прием данных в виде универсальной процедуры
* в зависимости от передаваемых параметров меняем выполняемые
* функции на удаленном сервере
*
* m.lckey_word - имя выполняемой функции
* m.lcparameter1,m.lcparameter2 - передаваемые параметры
* m.lcNewTableName - имя создаваемого курсора (если не пустое)
*
*
*
*
PROCEDURE message_read
LPARAMETERS m.lckey_word,m.lcNewTableName,m.lcparameter1,m.lcparameter2,m.lcparameter3,;
m.lcparameter4,m.lcparameter5,m.lcparameter6
m.lcreturn=.F.
LOCAL loProxy
TRY
loProxy=CREATEOBJECT("MSSOAP.SOapClient30")
WAIT WINDOW 'I am trying to connect to web server...' NOWAIT
loProxy.MSSoapInit(gcWebServ)
loProxy.ConnectorProperty("Timeout") = 90 * 1000 && milliseconds
WAIT WINDOW 'Receiving data...' NOWAIT
lcXML=loProxy.message_read(m.lckey_word,m.gclog,m.gcpsw,m.lcparameter1,m.lcparameter2,;
m.lcparameter3,m.lcparameter4,m.lcparameter5,m.lcparameter6)
CATCH TO oErr
m.lcmess='('+ALLTRIM(STR(oErr.Errorno))+') '+TRIM(oErr.Details)
MESSAGEBOX(m.lcmess,0,'We cannot connect to your Web Server. Error: ',30000)
m.gcerror_form=SUBSTR(('Error during receiving: ('+ALLTRIM(STR(oErr.Errorno))+') '+;
TRIM(oErr.Details)),1,254)
EXIT
FINALLY
RELEASE loProxy
ENDTRY
WAIT WINDOW 'Operation has been completed...' TIMEOUT 0.1
*waIT WINDOW lcXML
*? lcXML
IF TYPE('lcXML')="C" AND LEN(lcXML)>30
XMLTOCURSOR(lcXML,m.lcNewTableName,4)
SELECT &lcNewTableName
IF RECCOUNT()>0
m.lcreturn=.T.
ELSE
m.lcreturn=.F.
ENDIF
ELSE
IF EMPTY(m.lcNewTableName) && for executive functions
IF (TYPE('lcXML')="N" AND lcXML=1) OR (TYPE('lcXML')="C" AND EVALUATE('VAL(lcXML)>=1'))
m.lcreturn=.T.
ELSE
m.lcreturn=.F.
ENDIF
ELSE
WAIT WINDOW 'We are very sorry, but we could not retrieve data from Web server ' TIMEOUT 5
ENDIF
ENDIF
RETURN m.lcreturn
Немного заострим Ваше внимание на обработке ошибок в процедуре
message_read. Она построена на простом анализе возвращаемого или нет
параметра от удаленного Web Service. В принципе это все можно усложнить
в Вашем реальном приложении, но мне для практических нужд такой
нехитрой обработки вполне хватает.
Далее разработаем основную форму frmmain.scx . Тут есть небольшая
хитрость. Мы хотели, чтобы это приложение было как можно проще. Выбор
пал на formset. У нас две формы: одна для регистрации клиентов, а вторая
для непосредственно работы с данными. Если регистрация прошла
неудачно, подается команда CLEAR EVENTS, и программа заканчивает свою
работу.
На форме регистрации есть кнопка со странным названием "Д" - это кнопка
для разработчика (Developer). Она съэкономит много Вашего времени - то
есть при тестировании программы Вы автоматически будете проходить
процесс регистрации в систему. Не забудьте только в методе формы INIT
сделать ее видимой только для Вас:
* стандартный трюк, чтобы съэкономить время разработчика
IF _VFP.STARTMODE=0
THISFORM.cmdD.VISIBLE =.T.
ELSE
THISFORM.cmdD.VISIBLE =.F.
ENDIF
Для того, чтобы наша форма была все время наверху и активна в метод
формы ACTIVATE вносим код:
* помещаем наше окно в верхнем слое
DECLARE INTEGER SetForegroundWindow IN USER32 INTEGER
DECLARE INTEGER FindWindow IN USER32 STRING @ , STRING @
lnHWND=FindWindow(0, _Screen.Caption)
IF lnHWND>0
SetForegroundWindow(lnHWND)
ENDIF
Метод кнопки "Войти" наиболее сложен. Здесь уже идет вызов метода login
нашего Web Service с передачей в качестве параметров имени
пользователя и пароля. В результате получаем курсор ACCESS с
некоторыми свойствами, которые мы используем в приложении. В реальной
жизни это очень удобное место для передачи на клиент названия модулей
внутри программы, к которым у клиента есть доступ или даже передача
самих модулей; удачное место для проверки устарела ли программа
клиента или нет и установки более свежей версии (опять же передача ее
через Интернет). В общем, все ограничено только Вашей фантазией и
деньгами клиента. В конце производим установки интерфейса в
зависимости от прав клиента:
LOCAL loProxy
TRY
loProxy=CREATEOBJECT("MSSOAP.SOapClient30")
WAIT WINDOW 'Произвожу соединение с web server...' NOWAIT
loProxy.MSSoapInit(gcwebserv)
* устанавливаем время возможной задержки ответа от сервера
loProxy.ConnectorProperty("Timeout") = 90 * 1000 && milliseconds
WAIT WINDOW 'Идет прием данных...' NOWAIT
lcXML=loProxy.login(m.gclog,m.gcpsw)
CATCH TO oErr
m.lcmess='('+ALLTRIM(STR(oErr.ERRORNO))+') '+TRIM(oErr.DETAILS)
MESSAGEBOX(m.lcmess,0,'We cannot connect to your Web Server. Error: ',30000)
EXIT
FINALLY
RELEASE loProxy
ENDTRY
* теперь очень простая проверка на то, что вернулось на наш запрос
IF TYPE('lcXML')="C".AND.LEN(lcXML)>25
WAIT WINDOW 'Получаем из XML файла курсор...' NOWAIT
XMLTOCURSOR(lcXML,"Access",0)
* в реальной жизни я пищу в этот курсор имена программных модулей,
* к которым имеет доступ данный клиент - ну а пока одна строка
SELECT ACCESS
m.gnuser_id=ACCESS.User_ID
m.gcadmin=ACCESS.Admin
ELSE
=MESSAGEBOX("Нам очень жаль, но Вы не смогли зайти в систему...", 0 + 16 +0,;
"Ошибка при подключению к удаленному серверу.", 10000)
CLEAR EVENTS
ENDIF
WAIT WINDOW 'Прием данных завершен...' TIMEOUT 0.1
* делаем некоторые контролы видимыми только для администратора
IF m.gcadmin=1
thisformset.frmMAIN.cmdDeleteMessage.Visible =.t.
thisformset.frMMAIN.cmdAddUserNew.Visible = .t.
thisformset.frMMAIN.cmdEditUser.Visible = .t.
ELSE
thisformset.frmMAIN.cmdDeleteMessage.Visible =.f.
thisformset.frMMAIN.cmdAddUserNew.Visible = .f.
thisformset.frMMAIN.cmdEditUser.Visible = .f.
ENDIF
thisformset.frmLOGIN.Visible =.f.
thisformset.frmMAIN.Visible =.t.
thisformset.frMMAIN.Caption=('Система обмена сообщениями ('+alltrim(m.gclog)+')')
thisform.Release
После успешной регистрации клиент попадает на основную форму работы с
данными. Как обычно - данных нет. Они запрашиваются путем нажатия
кнопки "Освежить данные":
THISFORM.timer1.RESET
IF !EMPTY(m.gclog) AND !EMPTY(m.gcpsw)
IF USED('MESSAGES')
SELECT MESSAGES
m.lnMes_ID=MESSAGES.Mes_ID
SET FILTER TO
ELSE
m.lnMes_ID=0
ENDIF
IF message_read('READ_MESSAGES_START','MESSAGES',DATE(),THISFORM.spnDaysUpdate.VALUE)
THISFORM.grid1.RECORDSOURCE=''
THISFORM.grid1.RECORDSOURCE='MESSAGES'
THISFORM.grid1.INIT()
SELECT MESSAGES
IF !EMPTY(m.lnMes_ID)
LOCATE FOR m.lnMes_ID=MESSAGES.Mes_ID
ELSE
GOTO BOTTOM
ENDIF
thisformset.counter=thisformset.counter+1
THISFORM.lblmessage.CAPTION = 'Последнее обновление с сервера: '+;
TTOC(DATETIME())+' ('+ALLTRIM(STR(thisformset.counter))+')'
ELSE
THISFORM.grid1.RECORDSOURCE=''
ENDIF
ENDIF
THISFORM.grid1.REFRESH()
THISFORM.grid1.SETFOCUS()
THISFORM.timer1.RESET
Ничего сложного - некоторые нюансы только с установками Grid, так как
мы после приема данных заново воссоздаем курсор MESSAGES. Основную
работу мы возложили на метод INIT в нашем Grid (не забывая при этом
каждый раз очищать DataSource в основной программе вызова):
*
* пробуем построить красивый Grid
*
IF USED('MESSAGES') && на всякий случай проверим наличие открытого курсора
THIS.READONLY= .T.
THIS.DELETEMARK= .F.
THIS.SCROLLBARS= 2
THIS.GRIDLINES= 0
*!*
THIS.FONTSIZE= 9
*!*
THIS.ROWHEIGHT = 20
THIS.COLUMNCOUNT = 6
THIS.ALLOWCELLSELECTION = .F.
WITH THIS.column1
.ALIGNMENT=1
.WIDTH=60
.CONTROLSOURCE="MESSAGES.Mes_ID"
.header1.CAPTION='Сообщ. No'
.header1.ALIGNMENT=2
.READONLY=.T.
ENDWITH
WITH THIS.column2
.WIDTH=70
.ALIGNMENT=0
.CONTROLSOURCE='MESSAGES.User_nick'
.header1.CAPTION='Автор'
.header1.ALIGNMENT=2
.READONLY=.T.
ENDWITH
WITH THIS.column3
.WIDTH=100
.ALIGNMENT=0
.CONTROLSOURCE='MESSAGES.city'
.header1.CAPTION='Откуда'
.header1.ALIGNMENT=2
.READONLY=.T.
ENDWITH
WITH THIS.column4
.ALIGNMENT=0
.WIDTH=100
.FONTSIZE=8
.CONTROLSOURCE="MESSAGES.published"
.header1.CAPTION='Опубликовано'
.header1.ALIGNMENT=2
ENDWITH
WITH THIS.column5
.ALIGNMENT=0
.WIDTH=100
.FONTSIZE=8
.CONTROLSOURCE="MESSAGES.WASUPDATED"
.header1.CAPTION='Изменено'
.header1.ALIGNMENT=2
ENDWITH
WITH THIS.column6
.ALIGNMENT=0
.WIDTH=380
.CONTROLSOURCE="MESSAGES.TITLE"
.header1.CAPTION='Тема'
.header1.ALIGNMENT=2
.READONLY=.T.
ENDWITH
* раскрасим немного строки
THISFORM.grid1.SETALL("DynamicBACKColor", ;
"IIF(MESSAGES.User_to=m.gnuser_id , RGB(185,255,185),"+;
"IIF(MESSAGES.User_from=m.gnuser_id,RGB(217,217,255),RGB(255,255,255)) )", "Column")
* поощрим желание некоторых клиентов прочитать сообщение по двойному нажатии
* левой клавиши мышки (а заодно и по правой клавише)
BINDEVENT(THIS,"Dblclick",THISFORMSET,"Read_Message",1)
BINDEVENT(THIS,"Rightclick",THISFORMSET,"Read_Message",1)
ENDIF
Все строим "вручную". Может быть это и не очень эффективный прием
программирования, но зато наглядный. Очень полезна команда BINDEVENT,
появившаяся в VFP 8.0 - она одной строкой позволяет делать
"фантастические вещи". Единственное уточнение - метод "Read_Message"
создаем в главной FormSet.
Для автоматического обновления данных мы используем таймер,
параметры которого задают наши клиенты самостоятельно. Думаю, что они
будут довольны, получив некоторый контроль над событиями. При вызове
дополнительных форм надо не забывать его сбрасывать: для этого мы
используем свойство главного FormSet -appbusy. Все очень просто.
Из "вспомогательных" форм рассмотрим добавление нового пользователя
администратором. После непродолжительных раздумий мы решили лишить
клиентов возможности самим регистрироваться, но если Вы не согласны, то
можете изменить программу так, как считаете нужным.
В общем-то стандартная форма ввода данных. Интерес для нас
представляет метод Click() кнопки "Сохранить".
IF THISFORM.newmessage=.T.
IF EMPTY(THISFORM.txtUser_nick.VALUE)
MESSAGEBOX("Псевдоним(Login)","Пожалуйста, заполните следующее поле",64,3000)
THISFORM.txtUser_nick.SETFOCUS
RETURN
ENDIF
IF EMPTY(THISFORM.txtPassword.VALUE)
MESSAGEBOX("Пароль.",;
"Пожалуйста, заполните следующее поле",64,3000)
THISFORM.txtPassword.SETFOCUS
RETURN
ENDIF
* для простоты дальнейшего развития системы просто передаем весь курсор
* данный пример показывает как передавать на сервер курсор FoxPro
* все как обычно - в виде символьной строки (XML)
IF USED('USERS_PROF')
SELECT USERS_PROF
CURSORTOXML("USERS_PROF","lcXML",1,1,0,"1")
ELSE
lcXML=""
ENDIF
IF message_read('ADD_NEW_USER','',(THISFORM.text2.VALUE),lcXML)
THISFORM.RELEASE
ELSE
* в случае неудачи - просто снова возвращаемся в экран ввода нового сообщения
=MESSAGEBOX("Нам очень жаль, но Вы не смогли опубликовать данные...", 0 + 16 +0,;
"Ошибка при подключению к удаленному серверу.", 10000)
ENDIF
ELSE
IF EMPTY(THISFORM.txtUser_nick.VALUE)
MESSAGEBOX("Псевдоним(Login)","Пожалуйста, заполните следующее поле",64,3000)
THISFORM.txtUser_nick.SETFOCUS
RETURN
ENDIF
IF EMPTY(THISFORM.txtPassword.VALUE)
MESSAGEBOX("Пароль.",;
"Пожалуйста, заполните следующее поле",64,3000)
THISFORM.txtPassword.SETFOCUS
RETURN
ENDIF
* для простоты дальнейшего развития системы просто передаем весь курсор
* данный пример показывает как передавать на сервер курсор FoxPro
* все как обычно - в виде символьной строки (XML)
IF USED('USERS_PROF')
SELECT USERS_PROF
CURSORTOXML("USERS_PROF","lcXML",1,1,0,"1")
ELSE
lcXML=""
ENDIF
IF message_read('ADD_NEW_USER','',(THISFORM.text2.VALUE),lcXML)
THISFORM.RELEASE
ELSE
* в случае неудачи - просто снова возвращаемся в экран ввода нового сообщения
=MESSAGEBOX("Нам очень жаль, но Вы не смогли опубликовать данные...", 0 + 16 +0,;
"Ошибка при подключению к удаленному серверу.", 10000)
ENDIF
ENDIF
Часть проверок мы все-таки производим на стороне клиента, чтобы не
загружать наш сервер лишней работой. В этом примере показано, как
передать на сервер курсор. По умолчанию его размер не превышает 100
Kb. Для нас этого хватит, но на практике надо увеличить его размер.
Делается это путем нехитрых манипуляций с реестром Windows:
1. Запустим программу для редактирования реестра:
2. Найдем в реестре вхождение для ключа SOAPISAP:
3. Отредактируем значение для ключа MaxPostSize (по умолчанию это
значение около 102 Kb. Имейте ввиду, что это не размер передаваемого
Вами файла, в котором будут присутствовать XML тэги, так что размер
Вашего передаваемого файла будет много меньше этого значения).
Разумное значение для быстрого Интернета (512 Kb/s) считается в
пределах 4-8 Mbytes...
Остальные параметры мы рекомендуем оставить по умолчанию. Есть еще
один нюанс - при расшифровке принятого XML файла в настоящее время
тратится очень много компьютерных ресурсов (поэтому нами
рекомендуется передавать данные сразу в виде таблиц или текста с
разделителями, "обвернутых" в поле MEMO файла XML). Кроме того, делите
большие файлы на части и передавайте их по частям, поверьте, так выйдет
быстрее.
Скачать исходные тексты для Клиента можно отсюда (файл
ws_mes_client.zip 72 Kb) В архив включен даже откомпилированный файл
приложения.
Download