如何用VFP编写Web Service

如何用VFP编写Web Service
作者:BOE
文章来源:www.boeworks.net
写在前面

半年来,一直想写一篇关于用 Visual FoxPro 7 编写 Web Service 的文章,但总不成愿。这回不是我偷懒,为了了解这种新技术我和我的朋友们经历了种种“磨难”。这里略作回忆,也算是新春来临之际对“往年”的回眸——算是没有浪费太多的生命。

磨难一:发布向导不能正确发布Web Service。

7月份就拿到了 Visual FoxPro 7,查遍Sample,没有发现有关于 Web Service 的 Demo。但在Help里以及微软对 Visual FoxPro7 的宣传里,都信誓旦旦的:我们支持开发 Web Service…… 还好在 Msdn 里看到了一篇名为《Creating Web Services with Visual FoxPro》的文章。真的很不幸,按部就班做实验却不能正确发布 Web Service。经过反复研究,我认为这是一个 Bug,还好发现了能绕过这个Bug的路径。这个时候已经是10月份了……

磨难二:原来 Web Service 是无状态的!

真是浪费生命啊,还好我已经不是专业的 Visual FoxPro 程序员了,业余的消闲,没有压力。大家记得BOE上的我写的《Visual FoxPro 7 全新登场-- XML 在 COM 组件数据集传递中 的意义》一文吗?那篇东西的示例,原本是为本文的 Web Service 准备的:当代码仅编译为 COM 被使用时,一切都在我的设想中;而进一步发布为Web Service,怪问题产生了—— Web Service 不能记忆我对它命令。经研究原来 Web Service 是“无状态”的(“无状态”在前几年就看到过了,当时无法理解。没想到现在 Web Service 帮我理解了这个概念,也算因祸得福)。

磨难三:Xmltocursor()对中文支持有问题!

自以为已经能用 Visual FoxPro 编写一些小的 Web Service 应用了,于是向一位网友鼓吹:用Web Service做你那个应用吧!人家很仔细,第二天就告诉了我一个大问题:用 xmltocursor() 没法正确处理包含中文的 xml 文档,很多中文字符会被截断!昏倒,又是一个Bug!那个时候微软已经宣布将要发布Visual FoxPro7的SP1,于是我就一边等待SP1,一边寻找其他的解决方案。黄天不负有心人,west-wind竟然提供了这么一套类库wwXML,功能比Visual FoxPro7对XML的支持更强大,对中文支持也很好!

(经过测试,Visual FoxPro 7 的 SP1 已经解决了对中文字符的处理的问题)

磨难四:SP1竟然这么难安装!

1月16号,微软发布了 SP1。鬼知道,他们的安装程序是怎么做的——只能在Win9x下正确安装,win2000、xp 下都不行(听说有人在 Win2000 下成功安装了 SP1,真佩服他们的运气……)。还好论坛的 QXF 同志,把他在 win98 下安装的sp1后发现的更新文件打了包,分发给大家。现在,如果你装不上SP1 的话,只要在计算机里作如下处理就行了。

1.关闭Visual FoxPro7
2.拷贝文件DW15.EXE、DWINTL.DLL到Visual FoxPro7的HOME()目录。
3.在计算机里查找并替换VFP7.EXE、VFP7R.DLL、VFP7T.DLL、VFP7RENU.DLL、VFP7Runtime.MSM、VFPOLEDB.DLL、VFPOLEDB.MSM


闲话就说那么多了,让我们开始吧!

系统要求

1.各种版本的 Win2000及 Win Xp,并安装 IIS (其他版本的Windows没有试过)
2.安装Visual FoxPro7,建议安装SP1
3.安装 SOAP Toolkit 2.0(在 Visual FoxPro 7 安装盘里就有)
4.SQL Server 7 或者 SQL Server 2000

范例介绍

数据源

这个 Web Service 提供了两个数据查询的方法,分别是:检索系统中所有发票的功能以及查询某段时间中销售总额的功能。

我采用了SQL Server 的Demo数据库NorthWind为数据来源,为了凸现Visual FoxPro的威力,我有把这个数据库转换成为Visual FoxPro的本地DBC库。数据库的名称是:web_service.dbc,包含着13个表,这与SQL Server 里的NorthWind数据库是一致的!

好了,我们现在有两个一样的数据来源,一个是本地的DBC,另一个是远程的SQL Server数据库。在待会的试验中,我们会同时在这两个数据源中查询数据,大家就会看到 Visual FoxPro 在远程(异构)数据处理上的简便、灵活。

还有一些工作要做。我们知道,Visual FoxPro 访问远程数据库的方法是Remote View(远程视图) 和 SPT。其中 Remote View 是很有特色的,它的数据源是远程数据,但它本身又是本地 DBC 的成员,这样就能实现 Visual FoxPro 对远程数据源的快捷管理、完美融合。

这里根据需要我们要建立三个Remote View,分别对应 NorthWind数据库里的Orders、Order details 表和视图 invoices:可以通过如下命令完成:

CREATE SQL VIEW orders_sql REMOTE CONNECTION localSQLServer as select * from orders
CREATE SQL VIEW Order_details_sql REMOTE CONNECTION localSQLServer as select * from [order details]
CREATE SQL VIEW invoice_sql REMOTE CONNECTION localSQLServer as select * from invoices

这里用到连接“localSQLServer” 可以用如下命令生成:(大家可根据各人系统不同情况建立合法的连接!)

CREATE CONNECTION LocalSQLServer CONNSTRING "DRIVER=SQL Server;SERVER=BOEWORKS;UID=sa;PWD=;DATABASE=Northwind"

在试验中,我们还要用到由本地数据所产生的Local View(本地试图),它的效果与 invice_sql 一致,只不过前者的数据来源于本地的DBC,后者来源于SQL Server的NorthWind数据库。可以用如下代码实现:(如果你用视图设计器设计这个视图,你会发现视图设计器“报错”,这是因为:Visual FoxPro 的视图设计器不支持太过复杂的 SQL 语句。所以设计复杂视图时,应该直接手写代码,其实这也是众多SQL高手的通常做法!)

CREATE SQL VIEW invoice_vfp as ; 
SELECT Orders.shipname, Orders.shipaddress, Orders.shipcity,;
Orders.shipregion, Orders.shippostalcode, Orders.shipcountry,;
Orders.customerid, Customers.companyname AS customernam,;
Employees.firstname+" "+Employees.lastname AS salesperson,;
Orders.orderid, Orders.orderdate, Orders.requireddate,;
Orders.shippeddate, Shippers.companyname AS shippername,;
Order_details.productid, Products.productname, Order_details.unitprice,;
Order_details.quantity, Order_details.discount,;
Order_details.unitprice*Order_details.quantity*(1-Order_details.discount) AS extendedprice,;
Orders.freight;
FROM northwind!employees INNER JOIN northwind!orders;
INNER JOIN northwind!customers;
INNER JOIN northwind!shippers;
INNER JOIN northwind!order_details;
INNER JOIN northwind!products ;
ON Order_details.productid = Products.productid ;
ON Orders.orderid = Order_details.orderid ;
ON Orders.shipvia = Shippers.shipperid ;
ON ;
Orders.customerid = Customers.customerid ;
ON Employees.employeeid = Orders.employeeid

基类

#define CL chr(13)+chr(10)
DEFINE CLASS FoxBaseClass as Session 
DataSession=2
DataBasePath="D:/Data/"

PROCEDURE Init()
LOCAL cText as String
cText="开启时间"+TRANSFORM(TIME()) 
STRTOFILE(cText,'c:/FoxWebService.txt',.t.)

ENDPROC

PROCEDURE Destroy
LOCAL cText as String
cText="关闭时间"+TRANSFORM(TIME()) 
STRTOFILE(cText,'c:/FoxWebService.txt',.t.) 
ENDPROC


PROCEDURE OpenDataBase()
OPEN DATABASE this.DataBasePath+"northwind.dbc" SHARED 

ENDPROC

PROCEDURE CloseDataBase()
CLOSE DATABASES

ENDPROC

FUNCTION ConnectSQLServer() as Integer 
LOCAL iConn as Integer
iConn=SQLCONNECT("LocalSQLServer")
RETURN iConn

ENDFUNC

PROCEDURE DisConnectSQLServer(iConn as Integer)
SQLDISCONNECT(iConn)

ENDPROC

PROCEDURE Error(nError as Integer,cMethod as String, nLine as Integer)
LOCAL cText as String
cText=;
'出错时间:'+transform(datetime())+CL+;
'错误代码:'+str(nError,4)+CL+;
'错误提示:'+message()+CL+;
'错误方法:'+cMethod+CL+;
'错误行号:'+transform(nLine)+CL+CL
strtofile(cText,'c:/FoxWebService.txt',.t.)
comreturnerror("Fox Web Service",cText)
ENDPROC
ENDDEFINE

分析上面的代码,有几点需要讲解:

1.控制本地数据所在的路径

DataBasePath="D:/Data/" 

2.打开、关闭本地数据库:

PROCEDURE OpenDataBase()
PROCEDURE CloseDataBase()

3.连接到SQL Server和从SQL Server断开连接(其实本例中没有使用这两个方法,因为我们使用了远程视图。远程视图能够自动控制连接!)

FUNCTION ConnectSQLServer() as Integer 
PROCEDURE DisConnectSQLServer(iConn as Integer)

4.出错控制

PROCEDURE Error(nError as Integer,cMethod as String, nLine as Integer)

5.记录对象的建立与摧毁(这是为了证明Web Service是“无状态”的,具体我们等会再说)

具体实现代码

DEFINE CLASS FoxWebService as FoxBaseClass olepublic

FUNCTION GetInvoice(iType as Integer) as string
LOCAL cXML as String

this.OpenDataBase()

IF iType=0 &&SQL Server 数据表
USE invoice_sql ALIAS invoice 
ELSE &&Fox 数据库
USE invoice_vfp ALIAS invoice 
ENDIF 

CURSORTOXML("invoice","cXML",3,48,0,"")
USE IN invoice

this.CloseDataBase()

RETURN cXML

ENDFUNC

FUNCTION GetSumSales(dStart as Date,dEnd as Date) as Double

this.OpenDataBase()
SELECT sum(b.Unitprice*b.Quantity*(1-b.Discount)) as Sumsalse from orders a INNER JOIN order_details_sql b ON a.orderid=b.orderid ;
where a.orderdate>=dstart and a.orderdate<=dEnd into ARRAY result
IF _tally>0 then
RETURN result[1]
ELSE
RETURN 0
ENDIF

this.CloseDataBase() 
ENDFUNC


ENDDEFINE

从代码上我们可以发现,FoxWebService 是FoxBaseClass的子类,继承了所有FoxBaseClass的特性。同时它还被赋予了OlePublic关键字,表明它可以编译成为COM对象!

FoxWebService 只有两个方法,分别实现不同的两个功能,具体如下:

功能一:查询所有发票信息,用户可以指定从DBC本地数据库返回信息还是从SQL Server返回信息。

FUNCTION GetInvoice(iType as Integer) as string

当参数 iType 等于 0 时,从SQL Server 里返回Invoice信息;其他情况下,从本地的 DBC里返回Invoice 信息。具体通过调用两个不同的视图来实现:

IF iType=0 &&SQL Server 数据表
USE invoice_sql ALIAS invoice 
ELSE &&Fox 数据库
USE invoice_vfp ALIAS invoice 
ENDIF 

到这里,大家也许会问了,怎么把一个数据集合(表)返回出去,这个问题我在《Visual FoxPro 7 全新登场-- XML 在 COM 组件数据集传递中 的意义》一文里已经讲的很清楚了:用cursortoxml()把数据集合转化成为XML字符串返回:

CURSORTOXML("invoice","cXML",3,48,0,"")

功能二:返回一个时间段内的销售金额合计

如果您研究过数据库结构的话,这句查询是很容易完成的。但这里我加了一个有意思的要求:销售主档(orders)的数据要来源于本地的DBC,销售明细(Order Details)的数据要来源于远程 SQL Server 表。且不论这个要求是否合理(这里,我们只谈论技术实现),这显然是异构数据库之间的关联查询:用了Visual FoxPro 可以简单的实现:

SELECT sum(b.Unitprice*b.Quantity*(1-b.Discount)) as Sumsalse from orders a INNER JOIN order_details_sql b ON a.orderid=b.orderid ;
where a.orderdate>=dstart and a.orderdate<=dEnd into ARRAY result
IF _tally>0 then
RETURN result[1]
ELSE
RETURN 0
ENDIF

看到了吗?很简单——我这里实际上是关联了本地的Orders表与远程视图order_details_sql。order_details_sql打开时,Visual FoxPro会自动的通过ODBC 读取 SQL Server 上的有关数据,所以连接本地的Orders表与远程视图 order_details_sql 就等于直接连接异构数据表。这一点在其他语言中是很难实现的,它们也可以读取不同的数据源的数据,但它们无法支持异构数据在本地的SQL连接,它们只能用“循环+条件判断”的方式实现“异构数据”的连接。

编译并发布 Web Service

编译 Web Service

这个过程大家应该是非常熟悉了,其实就是编译成通常的COM。通过下面的命令就可以实现:

BUILD MTDLL First_web_service FROM First_web_service

好了,就这样容易——COM组件做好了,我们可以调用一下,在Command窗口中:

*请务必保证 C 盘根目录中不存在FoxWebService.txt文件,如果有就删除它(我们的目的是证明COM组件是“有状态”的)
*建立COM对象
ox=CREATEOBJECT("first_web_service.FoxWebService")
*测试GetInvoice
XMLTOCURSOR(ox.GetInvoice(0),"test")
*测试GetSumSales
?ox.GetSumSales({^1997-01-01},{^1997-3-30})
*摧毁COM对象
rele ox

如果测试成功了,就请查看C 盘根目录中的 FoxWebService.txt 文件,我的文件中的内容是(根据试验时间,结果会有不同):

开启时间23:20:54关闭时间23:21:25

这是由FoxWebService对象的 Init 和 Destory 事件中的代码产生的:Init当类被实例化为对象时触发,Destory当对象被摧毁时触发。在COM 应用中,CreateObject()和rele 分别触发了这两个事件!

好了,记住这里的结果。我们继续……

发布Web Service

在IIS中建立虚拟目录

这个步骤很简单,建立一个虚拟目录。设定好以后的属性页是:

这里我只设定了两个属性,其他保持默认状态。设名称为:First_web_service,设本地路径为DLL所在路径,这里是:D:/VFP7_Web_Service。

在Visual FoxPro 里发布 Web Service

工具-〉向导-〉Web Services

这里,COM Server里,我们选择刚才编译的那个DLL。

为了获得更多信息,按“Advanced…”按钮,弹出如下对话框:

这里 WSDL 文件是必须设定的,它描述这个Web Service 能够提供的服务,以及对外的接口。我们让它存放在虚拟目录下,文件名是FoxWebService.WSDL。

这里还有一个有用的选项就是“IntellSense Scripts”,关联Web Service与Visual FoxPro开发环境之间的关系:将Web Service的接口注册到IntellSense中,这样在Visual FoxPro里调用这个Web Service就会很方便的;当然,这里也可以不将Web Service 注册到 IntellSense 中,而在Visual FoxPro 里注册,具体方法参见:《Visual FoxPro 7 全新登场-- Web Service Client》。

还有一个选项“Automatically generate web service file during project build”,意思是:当重新编译项目生成COM时,同时建立新的Web Service文件;这个选项其实是一个Visual FoxPro的Project Hook,同样可以在Visual FoxPro的开发环境里设定。

这里填写完有关数据之后,按“OK”,回到图三。按动“Generate” 按钮,如果注册成功,将跳出对话框:

到这里,算是到公告成了。

测试Web Service

简单的测试代码

怎么使用一个Web Service,如果你看过《Visual FoxPro 7 全新登场-- Web Service Client》一文,应该是没什么问题的,这里只给出代码:

*请务必保证 C 盘根目录中不存在FoxWebService.txt文件,如果有就删除它(我们的目的是证明Web Service 是“无状态”的)
LOCAL oweb as VFPWebService_WS
LOCAL loWS
loWS = NEWOBJECT("Wsclient",HOME()+"ffc/_webservices.vcx")
loWS.cWSName = "VFPWebService_WS"
oweb = loWS.SetupClient("http://BOEWORKS/first_web_service/FoxWebService.wsdl", "FoxWebService", "FoxWebServiceSoapPort")
XMLTOCURSOR(oweb.GetInvoice(0),"test")
?oweb.GetSumSales({^1997-01-01},{^1997-3-30})

如果测试成功了,就请查看C 盘根目录中的 FoxWebService.txt 文件,我的文件中的内容是(根据试验时间,结果会有不同):

开启时间23:50:47关闭时间23:50:48开启时间23:50:50关闭时间23:50:50

无状态的Web Service

这里我们发现,在这段代码执行过程中,Web Service 对象创建、毁灭了两次;而几乎同样的代码,基于COM的应用,对象只创建、摧毁一次。——这就说明了Web Service是“无状态”的,客户程序每一次调用,就自动创建,调用完毕就自动摧毁。这里调用了两次,分别是调用GetInvoice()方法和GetSumSales()方法。这两次方法的调用是没有任何关系的,也就是说,Web Service是不会保存前一次调用后产生的状态,供以后调用时使用,这与传统的COM调用是有很大的区别的。

这里最有迷惑力的是这一段代码:

oweb = loWS.SetupClient("http://BOEWORKS/first_web_service/FoxWebService.wsdl", "FoxWebService", "FoxWebServiceSoapPort")

看上去等同于调用COM时使用的CreateObject(),我还没有仔细查看SOAP的文档,但我想这段代码只是请求Web Service的接口,并在客户机本地初始化一些东西,而对Web Service没有什么影响。

无状态的利弊

无状态的最大好处就是高效率,Web Service不是为几个客户提供服务的,它不可能记忆每一个客户端的请求状态,如果那样做,系统效率将很低很低。但是,如果Web Service不能知道客户程序的状态,也不能与客户程序进行很好的交互,不能很好的交互,就不能做出大的、好的项目。这是俄我想起了ASP,作为Web 应用,它也是无状态的,但为了实现服务器与客户机的交互(状态记忆),就提出了所谓的保存在客户端的 cookies 和服务器端的Session对象……以求效率与功能的平衡。

后记

Web Service 是很新、很流行的东西,很欣慰——Visual FoxPro 7 能很好的支持它的开发。虽然我在学习过程中遇到很多磨难,这不能说这种技术有什么不好,只能认为我的水平太次,真的应该好好学习了……

又是凌晨一点了,我很担心这篇东西的价值——有多少网友能顺利的把试验做下来,毕竟本文涉及了很多 Visual FoxPro 开发人员不熟悉的东西……

你可能感兴趣的:(如何用VFP编写Web Service)