使用ASP、VB和XML建立運行于互聯(lián)網(wǎng)上的應用程序(1)
在個(gè)人電腦上使用單機版應用軟件的時(shí)代很快就要過(guò)去了,現在大部分的應用程序都開(kāi)發(fā)出網(wǎng)絡(luò )版或大都需要共享網(wǎng)絡(luò )上豐富的數據資源。我們雖然寫(xiě)了很長(cháng)時(shí)間基于客戶(hù)端/服務(wù)器的應用程序,但是這些程序大部分只是運行在小型的局域網(wǎng)內部。然而,有很多客觀(guān)的原因要求我們要修改這些程序以使它們能夠運行在一個(gè)企業(yè)的內部網(wǎng)甚至是國際互聯(lián)網(wǎng)。
是什么原因迫使我們做呢?首先,隨著(zhù)一個(gè)企業(yè)的規模逐漸擴大,公司可能會(huì )跨地區甚至跨國經(jīng)營(yíng),每個(gè)分公司的員工的數量也會(huì )逐年增多,這些在外地的員工肯定需要頻繁地訪(fǎng)問(wèn)總公司的數據庫資源;其次,集中應用程序的數據資源,能夠使你更好的監控數據庫的訪(fǎng)問(wèn)和使用情況。第三,你可以通過(guò)從一個(gè)集中的位置獲取全局應用程序設置,從而維護和更新它們,最終達到緩減應用程序更新的目的。第四,盡量從Web服務(wù)器上訪(fǎng)問(wèn)數據庫而不是從客戶(hù)端上訪(fǎng)問(wèn)數據庫,這樣可以避免通過(guò)網(wǎng)絡(luò )傳送登錄信息和客戶(hù)密碼,從而避免安全隱患;而且,使用瀏覽器從后臺獲取數據,這樣能夠避免刷新整個(gè)頁(yè)面。
這就要求我們創(chuàng )建一個(gè)運行于互聯(lián)網(wǎng)上的應用程序,而假如想創(chuàng )建一個(gè)運行在HTTP協(xié)議上的VB程序,那么關(guān)鍵就是使用XML和XMLHTTPRequest對象。這個(gè)對象是Microsoft XML分析器(msxml.dll)的一部分,XMLHTTPRequest對象可以讓你通過(guò)HTTP向遠程服務(wù)器發(fā)送GET和POST請求,運行在遠程服務(wù)器上的程序接收這個(gè)請求,翻譯出它的內容,返回數據或者一個(gè)錯誤頁(yè)面到調用它的應用程序。對網(wǎng)絡(luò )編程有一些研究的朋友會(huì )看出我這個(gè)設想很象SOAP,但是在這里我不想使用SOAP,因為如果那樣的話(huà)會(huì )使程序變得很復雜。
想要改變一個(gè)完全獨立的客戶(hù)端單機版程序是不太現實(shí)的,但即使如此,從一個(gè)集中的服務(wù)器上下載應用程序設置也比使用本地的INI文件或Windows注冊標有更大的獨立性和靈活性。舉例來(lái)說(shuō),假設你有一支手機銷(xiāo)售隊伍,他們需要訪(fǎng)問(wèn)集中化的信息來(lái)更有效的銷(xiāo)售手機,每天,總公司集中收集數據,然后用電子郵件的形式發(fā)送給銷(xiāo)售人員。然而,市場(chǎng)的壓力和迅速變化的銷(xiāo)售形式勢必使銷(xiāo)售人員要訪(fǎng)問(wèn)最新的數據信息。但是,網(wǎng)絡(luò )管理員卻堅持拒絕讓在遠程客戶(hù)端的銷(xiāo)售人員訪(fǎng)問(wèn)總公司數據庫服務(wù)器,因為他們不想通過(guò)公用的網(wǎng)絡(luò )發(fā)送用戶(hù)名和登錄密碼。因此勢必要使用一種新的技術(shù)代替基于客戶(hù)端/服務(wù)器的技術(shù),不要著(zhù)急,我想看完本文你就會(huì )解決這個(gè)問(wèn)題的。
讓我們先分析一下客戶(hù)端/服務(wù)器應用程序。在一個(gè)標準的客戶(hù)端/服務(wù)器應用程序中,在應用程序開(kāi)始時(shí),你能夠初始化數據庫連接字符串,這就意味著(zhù),客戶(hù)有使用數據庫連接字符串的權利,這包括用戶(hù)名和口令。但是客觀(guān)情況如果不允許你在網(wǎng)絡(luò )上發(fā)送這些信息的話(huà),你就必需在不聯(lián)接數據庫的情況下直接從客戶(hù)端取得數據發(fā)送給客戶(hù)。那么解決方案之一就是在服務(wù)器上創(chuàng )建一個(gè)ASP頁(yè)(在本例中稱(chēng)為getData.asp)接收特定格式的POST數據,它要求一個(gè)包含XML字符串,用來(lái)創(chuàng )建ADO對象并運行存儲過(guò)程或動(dòng)態(tài)SQL語(yǔ)句命令。如果信息有效的話(huà),getData.asp執行存儲過(guò)程,并返回一個(gè)XML格式的數據集、返回值列表或錯誤頁(yè)面信息的XML字符串。對于返回數據的命令,客戶(hù)端要么重新實(shí)例化要么返回值或使用XML DOM(Document Object Model文檔對象模型)格式的錯誤頁(yè)面。
好,下面就讓我們來(lái)討論一下如何實(shí)現這個(gè)頁(yè)面吧!
getData.asp頁(yè)面首先使用一個(gè)DOMDocument對象來(lái)保存客戶(hù)端發(fā)送的數據:
‘創(chuàng )建DOMDocument對象
Set xml = Server.CreateObject ("msxml2.DOMDocument")
xml.async = False
然后,它裝載POST數據
‘裝載POST數據
xml.Load Request
If xml.parseError.errorCode <> 0 Then
Call responseError ("不能裝載XML信息。" & "Description: " & xml.parseError.reason & "<br>Line: " & xml.parseError.Line)
End If
它能夠返回commandtext元素值和returndata或returnvalue元素值。下面我只給出返回commandtext元素值的代碼,其余代碼請參看我下面所附的源程序。
Set N = xml.selectSingleNode("command/commandtext")
If N Is Nothing Then
Call responseError ("缺少 <sp_name> 參數。")
Else sp_name = N.Text
End If
接著(zhù),應該讓頁(yè)面創(chuàng )建一個(gè)Command對象,讀入所有<param>元素,并且為request中的每一個(gè)元素創(chuàng )建一個(gè)參數。最后,讓頁(yè)面打開(kāi)一個(gè)連接使用存儲過(guò)程adExecuteNoRecords選項來(lái)執行request。
set conn = Server.CreateObject("ADODB.Connection")
conn.Mode=adModeReadWrite
conn.open Application("ConnectionString")
set cm.ActiveConnection=conn
‘ 返回數據
if not returnsData then
cm.Execute
else
set R = server.CreateObject("ADODB.Recordset")
R.CursorLocation = adUseClient
R.Open cm, ,adOpenStatic, adLockReadOnly
end if
如果能夠返回數據的話(huà),那么returnData變量就為真值,并且把結果數據集返回到客戶(hù)端,仍然是一個(gè)XML文檔。
if returnsData then
R.Save Response, adPersistXML
if err.number <> 0 then
call responseError ("數據集發(fā)生存儲錯誤" & "在命令‘" & CommandText & "‘: " & Err.Description)
Response.end
end if
如果輸出參數返回值,那么這個(gè)頁(yè)面將返回一個(gè)包含這些值的XML字符串。文檔的根元素是一個(gè)<values>標記,每一個(gè)返回值都有其相應的子元素,如果發(fā)生任何錯誤,頁(yè)面都會(huì )格式化并返回一個(gè)包含錯誤信息的XML字符串:
Sub responseError(sDescription)
Response.Write "<response><data>錯誤: " & sDescription & "</data></response>"
Response.end
End Sub
假設在我們前面所說(shuō)的例子中,我們想在應用程序中顯示區域的左半邊顯示客戶(hù)的姓名列表,再在每個(gè)客戶(hù)姓名后面加上兩個(gè)鏈接:Purchase History和Recent Purchase。當用戶(hù)點(diǎn)擊其中的一個(gè)鏈接,客戶(hù)程序就會(huì )運行一個(gè)存儲過(guò)程并在右邊區域顯示出結果。 為了顯示這個(gè)想法的靈活性,我想讓用于返回數據的三個(gè)操作單元執行不同的工作過(guò)程,它們都調用getData.asp。首先,通過(guò)調用CustOrderHist來(lái)運行一個(gè)存儲過(guò)程,返回客戶(hù)的Purchase History,它搜索Northwind數據庫(為了方便起見(jiàn)我使用MS SQL中自帶的數據庫)并返回一個(gè)數據集。用于返回Recent Purchase 的查詢(xún)語(yǔ)句運行一個(gè)叫RecentPurchaseByCustomerID的存儲過(guò)程,來(lái)接收輸入的CustomerID參數并通過(guò)ProductName參數返回最近顧客購買(mǎi)的商品名。定義其處理過(guò)程相應SQL語(yǔ)句如下:
CREATE PROCEDURE RecentPurchaseByCustomerID @CustomerID nchar(5), @ProductName nchar(40) output AS SELECT @ProductName = (SELECT top 1 ProductName FROM Products INNER JOIN ([Order Details] INNER JOIN Orders ON Orders.OrderID=[Order Details].OrderID) ON Products.ProductID = [Order Details].ProductID WHERE Orders.OrderDate = (SELECT MAX(orders.orderdate) FROM Orders
where CustomerID=@CustomerID) AND Orders.CustomerID=@CustomerID) GO
不管你的查詢(xún)語(yǔ)句中含有動(dòng)態(tài)SQL語(yǔ)句還是含有返回記錄集的存儲過(guò)程或是輸出一個(gè)返回值,其處理POST消息的方法是一樣的:
set xhttp = createObject ("msxml2.XMLHTTP")
xhttp.open "POST", "http://localhost/myWeb/ getData.asp", False
xhttp.send s
好了,現在讓我們看一看如何發(fā)送和接收數據
客戶(hù)端的XML信息是由一個(gè)<command>元素和一些子元素組成:<commandtext>元素包含了存儲過(guò)程的名稱(chēng),<returnsdata>元素告訴服務(wù)器,客戶(hù)端是否要求接收返回數據,<param>元素包含參數信息。如果不使用參數的話(huà),那么最簡(jiǎn)單的發(fā)送字符串查詢(xún)就象下面這樣:
?。糲ommand>
?。糲ommandtext>
存儲過(guò)程或動(dòng)態(tài)SQL語(yǔ)句
?。?commandtext>
?。紃eturnsvalues>True</returnsvalues>
?。?command>
你可以為每一個(gè)參數使用一個(gè)<param>元素,來(lái)添加參數。每個(gè)<param>元素有五個(gè)子元素:name,type,direction,size和value。子元素的順序可以隨意調換,但是所有的元素都應當有不能缺少,我通常按照定義一個(gè)ADO對象的值的順序來(lái)定義它們。舉例來(lái)說(shuō),CustOrderHist存儲過(guò)程需要一個(gè)CustomID參數,所以用來(lái)創(chuàng )建發(fā)送到getData.asp的XML字符串的代碼為:
dim s
s = "<?xml version=""1.0""?>" & vbcrlf
s = s & "<command><commandtext>"
s = s & "CustOrderHist"
s = s & "</commandtext>"
s = s & "<returnsdata>" &True</returnsdata>"
s = s & "<param>"
s = s & "<name>CustomerID</name>"
s = s & "<type><%=adVarChar%></type>"
s = s & "<direction>" & <%=adParamInput%></direction>"
s = s & "<size>" & len(CustomerID)& "</size>"
s = s & "<value>" & CustomerID &"</value>"
s = s & "</param>"
s = s & "</command>"
注意,前面的代碼都是客戶(hù)端代碼,ADO常量是不在客戶(hù)端定義的-這就是它們?yōu)槭裁词褂茫? %>標記圍起來(lái)的原因。服務(wù)器在發(fā)送響應之前使用正確的值取代它們。getData.asp頁(yè)有一個(gè)Response.ContentType,它的屬性為"text/xml",這樣,你就可以使用ResponseXML屬性來(lái)返回結果了。當請求返回紀錄,你就可以創(chuàng )建一個(gè)Recordset對象并且使用XMLHTTP來(lái)打開(kāi)它:
Dim R
set R = createObject("ADODB.Recordset")
R.open xhttp.responseXML
當查詢(xún)語(yǔ)句返回數據時(shí),通過(guò)設置XMLHTTPRequest對象的responseXML屬性來(lái)創(chuàng )建一個(gè)DOMDocument:
Dim xml
set xml = xhttp.responseXML
輸出參數的XML字符串的每個(gè)返回值都包含一個(gè)元素,它們都是根元素<values>的直接子元素,例如:
?。?xml version=""1.0"" encoding=""gb2312""?>
?。紇alues>
?。紁aramname>value</paramname>
?。紁aramname>value</paramname>
?。?values>
如果你的數據使用別的國家的文字,你可能需要把編碼屬性用相應的編碼替換,例如對于大部分歐洲語(yǔ)言,可以使用ISO-8859-1
客戶(hù)端頁(yè)面使用返回的數據來(lái)格式化一個(gè)HTML字符串用于顯示,如:
document.all("details").innerHTML = <一些格式化的HTML字符串>
前面我們已經(jīng)介紹了使用ASP和XML混合編程,那是因為ASP頁(yè)面能夠很容易讓我們看清應用程序正在做什么,但是你如果你不想使用ASP的話(huà),你也可以使用任何你熟悉的技術(shù)去創(chuàng )建一個(gè)客戶(hù)端程序。下面,我提供了一段VB代碼,它的功能和ASP頁(yè)面一樣,也可以顯示相同的數據,但是這個(gè)VB程序不會(huì )創(chuàng )建發(fā)送到服務(wù)器的XML字符串。它通過(guò)運行一個(gè)名叫Initialize的存儲過(guò)程,從服務(wù)器取回XML字符串,來(lái)查詢(xún)ClientCommands表的內容。
ClientCommands表包括兩個(gè)域:command_name域和command_xml域??蛻?hù)端程序需要三個(gè)特定的command_name域:getCustomerList,CustOrderHist和RecentPurchaseByCustomerID。每一個(gè)命令的command_xml域包括程序發(fā)送到getData.asp頁(yè)面的XML字符串,這樣,就可以集中控制XML字符串了,就象存儲過(guò)程名字所表現的意思一樣,在發(fā)送XML字符串到getData.asp之前,客戶(hù)端程序使用XML DOM來(lái)設置存儲過(guò)程的參數值。我提供的代碼,包含了用于定義Initialize過(guò)程和用于創(chuàng )建ClientCommands表的SQL語(yǔ)句。
我提供的例程中還說(shuō)明了如何使用XHTTPRequest對象實(shí)現我在本文一開(kāi)始時(shí)許下的承諾:任何遠程的機器上的應用程序都可以訪(fǎng)問(wèn)getData.asp;當然,你也可以通過(guò)設置IIS和NTFS權限來(lái)限制訪(fǎng)問(wèn)ASP頁(yè)面;你可以在服務(wù)器上而不是客戶(hù)機上存儲全局應用程序設置;你可以避免通過(guò)網(wǎng)絡(luò )發(fā)送數據庫用戶(hù)名和密碼所帶來(lái)的隱患性。還有,在IE中,應用程序可以只顯示需要的數據而不用刷新整個(gè)頁(yè)面。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。