Rick Strahl写的关于使用VFP构建基于Web的DataService,很好的为我们阐述了整个DataService的数据流程,不管你是使用何种方法构建Webservice的,这篇文章在架构上都有指导意义.花了整整两天翻译,希望与大家共享,水平有限,不当之处请大家指出.
使用 Visual FoxPro提供一个基于互联网的数据服务
Source Code:
http://www.west-wind.com/presentations/foxwebDataService/FoxWebDataService.zip
你有没有想过在你的程序中建立这样一种远程数据访问机制:你将不仅可以从本地网络中访问数据,而且可以从 Web上轻易的访问数据。你仅仅需要做的是指向一个URL,并且执行一些Sql语句,那么客户端中就可以得到你需要的数据了,这是不是很Cool呢?在以下这篇文章中,Rick将会为我们展示怎样利用Visual FoxPro做到这一切的。
Source Code:
http://www.west-wind.com/presentations/foxwebDataService/FoxWebDataService.zip
你有没有想过在你的程序中建立这样一种远程数据访问机制:你将不仅可以从本地网络中访问数据,而且可以从 Web上轻易的访问数据。你仅仅需要做的是指向一个URL,并且执行一些Sql语句,那么客户端中就可以得到你需要的数据了,这是不是很Cool呢?在以下这篇文章中,Rick将会为我们展示怎样利用Visual FoxPro做到这一切的。
如果你正在使用任何一种类库,你也许知道其中关于数据访问的那些类。他们都不约而同的都提供了访问不同数据源的方法,一个标准的数据访问层会提供针对某一类型的数据源执行各种SQL语句的功能。数据访问层总是使用不同的数据连接器(data connector)来访问数据源,有时使用VFP的SQL命令或任何DML类型的SQL命令,有时使用SQLPassthrough或OleDb或CursorAdapter等较低级别的数据连接器。
在这篇文章中,我编写了一些有关数据访问的类,以便提供一个可以和Web XML Service进行通信的接口。这个项目分为两部分:1.一个类似代理的客户端,他负责通讯中请求(Request)的编码与响应(Response)的解码。2.一个服务器端,他负责通讯中请求的解码,逻辑的执行与响应的编码。所有的消息(Message)我们都使用XML格式。这个项目需要你的Web服务器可以执行Visual FoxPro代码,类似于Asp+COM,Web connection,Active FoxPro Page,FoxISAPI。我将在一开始使用Web Connection(因为他比较简单易用),最后我将使用ASP+COM的方式来实现。
Web based SQL access
第一步我们需要建立Sql 代理与服务器端。我们这样做的目的是让Sql代理可以以XML的方式传递Sql命令和一些指令给服务器端上的程序进行处理,注意,Sql代理发送的请求(request)与服务器端的响应(Response)都是以XML形式传递的。
我现在要建立一个简单的类,其中只有一个方法(Method)—Execute()用来传送Sql命令。这个方法与VFP中的SQL Passthrough的SQLExecute十分类似,并且他们都返回同一类型的值。
如果你考虑使用VFP来实现这个类将会简单的多,因为VFP提供了许多工具可以帮助我们。在VFP中我们有一个动态执行引擎(Macros和Eval)或SQL Passthrough来执行我们的命令 ,我们还有CUSORTOXML和XMLTOCURSOR等XML转换工具。我们唯一需要做的就是规定一种XML消息格式, 用以在Client与Server之间传递。
在Client端只有一个用来处理XML消息的代理类,他将你的Sql命令转换为XML,并以请求的形式发给Server进行处理。Server处理你发来的SQL命令,并将结果以XML的形式发回给Client。Client将发回的XML解码为游标(cursor),错误信息或返回值。图一为我们解释了这个过程:

图一:wwwHTTPSql类将Sql命令传递给Web服务器,由Web 服务器上的wwwHTTPSqlServer进行处理并将结果返回给Client。
Client与Server之间的通信是通过wwwHTTPSql类与wwwHTTPSqlServer类来实现的。wwwHttpSqlServer类可以集成在任何支持VFP的Web程序中,比如Web Connection ,ASP,FoxISAPI,Active FoxPro Page,ActiveVFP等.但你必须保证wwwHttpSqlServer在Web 服务器上运行,以便Client进行连接。
XML通过字符或DOM节点的形式传递,所以他十分适合在不同的环境中工作。图一向我们展示了wwwHTTPSql与wwwHTTPSqlServer类之间的关系。
在我深入讲解之前,让我们快速浏览一下Client上的代码,Client使用wwwHttpSql类来和特定Url上的Web Server进行连接并获取数据的。
Listing 1: Running a remote SQL Query with wwHTTPSql
DO wwHTTPSQL && Load Libs
oHSQL = CREATEOBJECT("wwHTTPSQL")
oHSQL.cServerUrl = "http://www.west-wind.com/wconnect/wwHttpSql.http"
oHSQL.nConnectTimeout = 10
*** Specify the result cursor name
oHSQL.cSQLCursor = "TDevelopers"
*** Plain SQL statements
lnCount = oHSQL.Execute("select * from wwDevRegistry")
IF oHSQL.lError
? oHSQL.cErrorMsg
ELSE
BROWSE
ENDIF
这段程序中你需要注意的是:1.你需要访问的Web Server(在这里我们使用Web Connection Server)的Url的设定 2:你传递的Sql命令。这些都是最基本的设定,因为wwwHttpSql继承自wwwHttp类,所以我们还可以设定一些诸如验证,连接等功能。
Client可以通过nResultModel属性设置返回数据的方式,默认(nResultModel=0)是返回VFP游标,nResultModel=2表示以XML的形式返回,此时cResponserXML的值为XML。nTransportMode属性让你选择数据传送的方式, nTransportMode=1表示使用VFP的CURSORTOXML命令,nTransportMode=0表示使用XML格式,nTransportMode=2表示使用二进制格式(Binary)(与VFP Cursor比较起来,如果数据量较大的话,使用二进制格式会更有效率)。简而言之,你可以适当的配置这些属性,使你在任何情况下更有效的处理数据。
Execute方法用以执行Sql命令,wwwHttpSql类将会知道返回的是游标或XML消息或任何其他值,他还会捕捉错误与处理返回的XML响应。Client 与Server端其实只是处理XML消息,所以我们只需要任何方法拼凑出合适的经过验证的XML消息发给已知URL的Web Server,而没必要一定使用VFP,比如我们可以在.Net中将将Dataset解析为XML。
Client端生成如下的XML消息发送给Server。
<wwhttpsql>
<sql>select * from wwDevRegistry</sql>
<sqlcursor>TDevelopers</sqlcursor>
<transportmode>1</transportmode>
</wwhttpsql>
当client上执行Execute()命令时,会执行以下步骤:
-
- 根据你在程序中设置的属性,生成如上的XML。
- XML被发送到指定的Url。
- Server端处理请求并返回XML形式的结果。一般来说,结果都是以XML形式返回的,除非发生了硬件错误,而软件错误也会以XML形式返回。
- Client收到响应信息并进行解析。
- 首先会验证响应信息是否为XML形式的,如果不是,则产生一个错误(Error),请求失败。
- 如果响应信息中包含错误的XML节,则请求失败,将错误信息赋值给IError和CErrorMSg。
- 如果响应的XML信息中包含一个返回值,那我们将他赋值给VReturnValue。
- 如果在响应的XML信息中包含一个游标并且nResultMode=0,我们将游标赋值给cSQLCursor属性。
如果你回想一下这个过程,你就会发现,在VFP和wwXML class类的帮助下,我们只需要很少的代码就可以完成上述功能,下面就是wwwHttpSql的核心代码,我们来看一下:
Listing 2: The core code of the wwHTTPSql client class
***********************************************************
* wwHTTPSQL :: CreateRequestXML
****************************************
FUNCTION CreateRequestXML()
LOCAL lcXML
loXML = THIS.oXML
lcXML = ;
"<wwhttpsql>" + CRLF + ;
loXML.AddElement("sql",THIS.cSQL,1) + ;
loXML.AddElement("sqlcursor",THIS.cSQLCursor,1) + ;
IIF(!EMPTY(THIS.cSQLConnectString),;
loXML.AddElement("connectstring",THIS.cSQLConnectString,1),[]) +;
IIF(!EMPTY(THIS.cSkipFieldsForUpdates),loXML.AddElement("skipfieldsforupdates",;
THIS.cSkipFieldsForUpdates,1) +CRLF,[]) + ;
IIF(THIS.nTransportMode # 0,;
loXML.AddElement("transportmode",THIS.nTransportMode,1),[]) +;
IIF(THIS.nSchema = 0,loXML.AddElement("noschema",1),[]) +;
IIF(!EMPTY(THIS.cSQLParameters),CHR(9) + "<sqlparameters>" + CRLF + ;
THIS.cSQLParameters + ;
CHR(9) + "</sqlparameters>" + CRLF,"")
IF THIS.lUTF8
lcXML = lcXML + loXML.AddElement("utf8","1",1)
ENDIF
lcXML = lcXML + "</wwhttpsql>"
THIS.cRequestXML = lcXML
RETURN lcXML
**********************************************************************
* wwHTTPSQL :: Execute
****************************************
FUNCTION Execute(lcSQL)
LOCAL lnSize, lnBuffer, lnResult, llNoResultSet, lcXML
lcSQL=IIF(VARTYPE(lcSQL)="C",lcSQL,THIS.cSQL)
THIS.cSQL = lcSQL
THIS.lError = .F.
THIS.cErrorMsg = ""
IF !INLIST(LOWER(lcSQL),"select","create","execute")
llNoResultSet = .T.
ELSE
llNoResultSet = .F.
ENDIF
*** Create the XML to send to the server
lcXML = THIS.CreateRequestXML()
THIS.nHTTPPostMode = 4 && Raw XML
THIS.AddPostKey("",lcXML)
THIS.cResponseXML = THIS.HTTPGet(THIS.cServerUrl,;
THIS.cUserName,THIS.cPassword)
*** Clear the entire buffer
THIS.AddPostKey("RESET")
THIS.AddSqlParameter()
IF THIS.nError # 0
THIS.lError = .T.
RETURN -1
ENDIF
THIS.nResultSize = LEN(THIS.cResponseXML)
IF EMPTY(THIS.cResponseXML)
THIS.cErrorMsg = "No data was returned from this request."
THIS.nError = -1
THIS.lError = .T.
RETURN -1
ENDIF
RETURN this.ParseResponseXml()
************************************************************************
* wwHttpSql :: ParseResponseXml
****************************************
FUNCTION ParseResponseXml()
LOCAL lcFileName, loDOM, loRetVal, cResult, ;
loError, loSchema, loXML
loXML = this.oXml
loDOM = loXML.LoadXML(THIS.cResponseXML)
THIS.oDOM = loDOM
*** Check for valid XML
IF ISNULL(loDom)
THIS.cErrorMsg = "Invalid XML returned from server" +;
loXML.cErrorMsg
THIS.nError = -1
THIS.lError = .T.
RETURN -1
ENDIF
*** Check for return value
loRetVal = loDom.documentElement.selectSingleNode("returnvalue")
IF !ISNULL(loRetval)
THIS.vReturnValue = loRetVal.childnodes(0).Text
ENDIF
*** Check for results that don't return a cursor
lcResult = Extract(THIS.cResponseXML,"<result>","</result>")
IF lcResult = "OK"
RETURN 0
ENDIF
*** Check for server errors returned to the client
loError = loDom.documentElement.selectSingleNode("error")
IF !ISNULL(loError)
THIS.cErrorMsg = loError.selectSingleNode("errormessage").text
THIS.nError = -1
THIS.lError = .T.
RETURN -1
ENDIF
*** OK we have an embedded cursor
*** Force new table instead of appending
IF USED(THIS.cSQLCursor)
SELE (THIS.cSQLCursor)
USE
ENDIF
IF "<VFPData>" $ LEFT(THIS.cResponseXML,100)
*** Use VFP 7's XMLTOCURSOR natively (faster)