前言
建立Web Service
建立Web Service Client 端程式
使用其他語言建立的 Web Service
與資料庫結合
結論
參考資料
前言
VFP 這項產品一直被謠傳微軟將不再支援,但在今年六月,微軟將VFP7自Visual Studio.NET中獨立出來銷售,總算是打破了這個謠言。此外,也為這項產品添加了不少功能,比如 Intellisense、對COM的更完整支援、SOAP與Web Service 等等。這裡就為各位解說新增的SOAP與Web Service功能。
SOAP 與 Web Service 這兩個名詞,相信我不用再多做解釋了,大家在這一陣子的耳濡目染之下,應該都大致了解,在此就不再贅述。如果想了解的人請參考前幾期RUN!PC李維老師的文章,有詳細的解說。
建立Web Service
首先讓我們來製作 Web Service,稍後,再來撰寫如何使用 Web Service 的 Client 端程式。
要使用VFP7製作Web Service之前,請先在[Internet 服務管理員]中,新增一個虛擬目錄,在此,我們先命名為 WebServices。
接下來得製作一個COM元件,在這裡我們就以一個簡單的相加函數來作為一個範例。
請先建立一個新專案,名稱叫做MyWebService,在Project Manager(專案管理員)中,切換到 Program頁籤,點選 ”New”,將下列這段程式碼貼到裡面去,然後存檔為 prog1.prg。
DEFINE CLASS MyCalculator AS session OLEPUBLIC Name = "MyCalculator" PROCEDURE doAdd Parameters A , B Return A+B ENDPROC ENDDEFINE
這一段程式碼只是將傳入的兩個參數做一個簡單的相加而已。
接著就要進行編譯的工作了,點選”Build”,建立的型態選為”Single-threaded COM Server(dll)”,按下 ”確定” 來建立一個dll型態的COM伺服器。
建立完成後,你會發現VFP7為你產生了一個mywebservice.dll,並已經為你註冊到電腦中了,讓我們驗證一下VFP7是否已經編譯成功,請在命令列中依序輸入:
obj=createobject(“MyWebService.MyCalculator”) ? obj.doAdd(100,100)
如果成功的話,你應該會看到VFP7回應了 200 這個答案,表示COM伺服器已經建立成功。
VFP7提供了一個方便的精靈,讓你可以直接在VFP7的整合環境中建立 Web Service,而不需要另外去執行 SOAP toolkit 來製作。
請你在Project Manager中,以滑鼠右鍵點選叫出快捷選單,選擇Builder。此時,會出現一個選擇精靈的對話方盒(如圖),選擇Web Services Publisher,並按下確定。
接著會出現Web Services Publisher這個對話盒(如圖),我們按下 “…” 按鈕,去搜尋我們剛剛建立的MyWebService.MyCalculator COM伺服器。找到並確定之後,再按下”Advanced”,讓我們看看可以再多做些什麼設定。
在 Advanced 設定中,我們可以指定
- WSDL 產出的位置。
- SOAP Listener型態。
- IntelliSense的Script名稱。
- 告訴VFP7在Project Build之後,是否自動產出相關的 Web Service 檔案。
- 產出的WSDL檔案是否要以UTF-16 Unicode作為預設編碼。
在確定所有設定無誤之後,按下 Generate,Web Service Publish精靈就會利用SOAP toolkit為我們在指定的位置產出Web Service的相關檔案。
Web Service Client 程式
那麼,我們要如何使用這個 Web Service 呢?
讓我們寫一個簡單的 form 來呼叫
請開一個新檔案,類型選為Form (表單),在表單裡面放置一個按鈕(如下圖),在按鈕的OnClick事件內放置如下的程式碼:
x = CREATEOBJECT("MSSOAP.SoapClient") x.MSSoapInit("http://ellery/webservices/MyCalculator.WSDL", , "MyCalculatorSoapPort") =messagebox( str( x.doAdd( 100,100 ) ) )
執行這個表單,在click按鈕之後,你會發現你並沒有辦法呼叫!!是VFP7的bug嗎??
首先,因為soap物件會利用 wsdl 檔案做 Initialization 的動作,所以我檢查了一下產出的 wsdl:
…略… <message name='MyCalculator.doAdd'> </message> <message name='MyCalculator.doAddResponse'> <part name='Result' type='xsd:anyType'/> </message> …略…
在這裡 SOAP toolkit wizard 並沒有為我們的 COM 元件產生參數的宣告。
產出的 WSDL 碼應該是下面這樣子才對的呀.
…略… <message name='MyCalculator.doAdd'> <part name='A' type='xsd:int'/> <part name='B' type='xsd:int'/> </message> <message name='MyCalculator.doAddResponse'> <part name='Result' type='xsd:anyType'/> </message> …略…
為了這個疑惑,我將問題貼到 Microsoft Newsgroup(http://communities.microsoft.com/newsgroups/default.asp) 和 Universal Thread(http://www.universalthread.com) 網站去詢問,過了兩天,就有了結果,兩個地方給我的回答都是相同的,我們必須將PROCEDURE doAdd 的宣告修改為
PROCEDURE doAdd(A as Integer, B as Integer)
並把 parameters 敘述移除。
修改後,重新 Build、利用 Wizard 發布之後,再執行剛剛我們建立好的 client 端Form,你應該就可以看到Web Service 被起始後的結果了。
使用其他語言建立的web service
為了驗證一下,VFP也能使用其他語言或其他平台上建立的 Web Service,我們仿效李維老師於RUN!PC 90 期文章中所寫的範例,寫一個取得溫度的簡單範例。
請依照下圖來製作一個表單:
然後,在”取得溫度”按鈕的 onclick 事件中,置入下列程式碼:
LOCAL obj obj = CREATEOBJECT("MSSOAP.SoapClient") obj.MSSoapInit("http://www.xmethods.net/sd/2001/TemperatureService.wsdl") thisform.text2.value=obj.getTemp(thisform.text1.value)
在執行這個表單之後,在州代號的欄位中,輸入 07060,再按下 “取得溫度” 按鈕,下面的當地氣溫欄位便顯示出氣溫,表示可以正確的呼叫!
與資料庫結合
VFP為了因應 XML 的流行,在這個版本中添加了與XML 相關的三個函數:CURSORTOXML()、XMLTOCURSOR()、XMLUPDATEGRAM(),正好可以用來在 Web Services 中傳遞資料。
請在Project Manager中,新增一個程式檔(program),然後將下列的程式碼加入,並命名為 prog2.prg:
DEFINE CLASS tastrade AS session OLEPUBLIC Name = "tastrade" PROCEDURE getAllEmployee() LOCAL handle as Integer LOCAL lc_xml as String handle=SQLSTRINGCONNECT("Driver=Microsoft Visual FoxPro Driver;” + ; “UID=;PWD=;” + ; “SourceDB=C:\WebService\Data\testdata.dbc;” + ; “SourceType=DBC;Exclusive=No;BackgroundFetch=Yes;” + ; “Collate=Machine;Null=Yes;Deleted=Yes;") =SQLEXEC(handle, ; "select emp_id, last_name, first_name, title from employee order by emp_id", ; "employees") =sqldisconnect(handle) CursorToXML("employees", "lc_xml") RETURN lc_xml ENDPROC PROCEDURE updateEmployee( c_request as string ) LOCAL handle LOCAL o_xmlDom, o_nodelist1, o_nodelist2, o_nodeEmployee, o_node LOCAL strWhere as String , strSet as String , strSql as String LOCAL ll_cycle as Boolean , ln_ret as Integer c_request=STRTRAN( c_request, 'encoding="Windows-1252" ', "" ) o_xmlDom=CreateObject("Microsoft.XMLDOM") o_xmlDom.async="false" o_xmlDom.loadXML(c_request) * parse xml and combine update-sql syntax IF o_xmlDom.hasChildNodes() ELSE RETURN ENDIF * create connection handle=SQLSTRINGCONNECT("Driver=Microsoft Visual FoxPro Driver;” + ; “UID=;PWD=;” + ; “SourceDB=C:\WebService\Data\testdata.dbc;” + ; “SourceType=DBC;Exclusive=No;BackgroundFetch=Yes;” + ; “Collate=Machine;Null=Yes;Deleted=Yes;") strWhere="" strSet="" l_cycle=.F. for each o_nodelist1 in o_xmlDom.documentElement.childNodes FOR EACH o_nodelist2 IN o_nodelist1.childNodes * initialize variables if o_nodelist2.nodeName="updg:before" FOR EACH o_nodeEmployee IN o_nodelist2.childNodes FOR EACH o_node IN o_nodeEmployee.childNodes IF o_Node.nodeName="emp_id" strWhere=strWhere+o_Node.nodeName+"="+"'" + ; TRANSFORM(o_Node.text,"@J 999999") + "' and " ELSE strWhere=strWhere+o_Node.nodeName+"="+"'" + ; o_Node.text + "' and " ENDIF NEXT NEXT ENDIF IF o_nodelist2.nodeName="updg:after" IF NOT o_nodelist2.hasChildNodes() ELSE FOR EACH o_nodeEmployee IN o_nodelist2.childNodes FOR EACH o_node IN o_nodeEmployee.childNodes IF o_Node.nodeName="emp_id" strSet=strSet+o_Node.nodeName+"="+"'" + ; TRANSFORM(o_Node.text,"@J 999999") + "'," ELSE strSet=strSet+o_Node.nodeName+"="+"'" + o_Node.text + "'," ENDIF NEXT NEXT ENDIF l_cycle=.T. ENDIF IF l_cycle = .T. * generate sql syntax strWhere=LEFT(strWhere,LEN(strWhere)-4) IF EMPTY(strSet) strSql="Delete from employee where "+ strWhere ELSE strSet=LEFT(strSet,LEN(strSet)-1) strSql="Update employee set " + strSet + " where " + strWhere ENDIF * call sqlexec() to send update-sql * and we can update our database ln_ret=SQLEXEC(handle,strSql) l_cycle=.F. strWhere="" strSet="" ENDIF NEXT NEXT * disconnect =sqldisconnect(handle) ENDPROC ENDDEFINE
這段程式碼裡面包含了一個類別,類別裡面包含了兩個方法:
getAllEmployee 利用 SPT 這組函數取得所有員工的員工編號、姓、名以及職稱,然後再利用 CURSORTOXML() 函數將 Cursor 資料轉換為 XML並傳回。
UpdateEmployee 則依據傳來的 update xml 資料作分析並更新。
再依照之前建立 Web Service 的步驟再做一次 Build COM 元件以及發布的動作。
接著,讓我們來撰寫 client 端的畫面,請依照下圖放置一個 Grid 控制項還有兩個 Button 控制項,然後同樣地,將下列的程式碼分別置放到兩個按鈕的 onclick 事件中。
在 Retrieve 按鈕的 onclick 事件中置放如下程式碼:
LOCAL x as Object , lc_xml as String *因為在[Update]按鈕中的 xmlupdategram() 函數需要設製 buffermode 並將 multilocks 設為 True SET MULTILOCKS ON IF USED("employee") thisform.grid1.RecordSource="" USE IN employee ENDIF x = CREATEOBJECT("MSSOAP.SoapClient") x.MSSoapInit("http://ellery/webservice/tastrade.WSDL") lc_xml=x.getAllEmployee( ) XMLToCursor(lc_xml,"employee") thisform.grid1.RecordSource="employee" thisform.Refresh() SELECT employee =CURSORSETPROP("buffering",5)
在 Update 按鈕的 onclick 事件中置放:
LOCAL lc_ret LOCAL x, lc_xml *檢查是否已經取得資料 IF NOT USED("employee") =MESSAGEBOX("[Retrieve] button not pressed!") RETURN ENDIF *產生異動的 xml lc_ret=XMLUPDATEGRAM("employee") IF EMPTY(lc_ret) &&如果沒有異動,發出警告 =MESSAGEBOX("You don't update any thing") ENDIF =MESSAGEBOX(lc_ret,"XMLUPDATEGRAM() generates following codes") *將資料更新回去! x = CREATEOBJECT("MSSOAP.SoapClient") x.MSSoapInit("http://ellery/webservice/tastrade.WSDL") x.updateEmployee( lc_ret ) SELECT employee
然後將這個表單命名為 form2,然後讓我們執行這個表單。
當我們按下 Retrieve 時,這個 client 端的表單就會為我們取回 Server 端的XML資料,並利用 XMLTOCURSOR() 轉換為 Cursor,以便能順利與 VFP 的控制項結合。
當按下更新的時候,我們也能利用 XMLUPDATEGRAM() 函數取得需要更新的必要資料,送回 Server 端,讓 Service 端能根據這份資料來更新資料。
結論
VFP 在7.0版之後就不再被包含在 Visual Studio.NET 之中,但是,VFP開發小組承諾會繼續為這個產品添加更多的功能以迎合使用者的需求。就我個人而言,對 VFP 仍懷有一份特殊的情感,尤其是在使用其他語言開發資料庫時,有時候仍然會忍不住有 “唉!怎麼沒有這個功能,VFP就有” 這個想法,所以我還是會繼續看相關的 VFP 資料。
不過,我感覺到微軟雖然對 VFP 的使用者作出了相當的承諾,可是,從 5.0 到 6.0 到 7.0 ,除了使用者介面與開發COM元件的支援外,其實並沒有什麼太大的改進,我想很多人也會和我有一樣的看法吧。
參考資料:
MSDN – Walkthrough: Creating Web Services with Visual FoxPro
RUN!PC 90 期
XML 網頁製作徹底研究 旗標出版