写在前面
半年来,一直想写一篇关于用 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 分别触发了这两个事件!
好了,记住这里的结果。我们继续……