使用ASP、VB和XML建立运行于互联网上的应用程序(1)
在个人电脑上使用单机版应用软件的时代很快就要过去了,现在大部分的应用程序都开发出网络版或大都需要共享网络上丰富的数据资源。我们虽然写了很长时间基于客户端/服务器的应用程序,但是这些程序大部分只是运行在小型的局域网内部。然而,有很多客观的原因要求我们要修改这些程序以使它们能够运行在一个企业的内部网甚至是国际互联网。
是什么原因迫使我们做呢?首先,随着一个企业的规模逐渐扩大,公司可能会跨地区甚至跨国经营,每个分公司的员工的数量也会逐年增多,这些在外地的员工肯定需要频繁地访问总公司的数据库资源;其次,集中应用程序的数据资源,能够使你更好的监控数据库的访问和使用情况。第三,你可以通过从一个集中的位置获取全局应用程序设置,从而维护和更新它们,最终达到缓减应用程序更新的目的。第四,尽量从Web服务器上访问数据库而不是从客户端上访问数据库,这样可以避免通过网络传送登录信息和客户密码,从而避免安全隐患;而且,使用浏览器从后台获取数据,这样能够避免刷新整个页面。
这就要求我们创建一个运行于互联网上的应用程序,而假如想创建一个运行在HTTP协议上的VB程序,那么关键就是使用XML和XMLHTTPRequest对象。这个对象是Microsoft XML分析器(msxml.dll)的一部分,XMLHTTPRequest对象可以让你通过HTTP向远程服务器发送GET和POST请求,运行在远程服务器上的程序接收这个请求,翻译出它的内容,返回数据或者一个错误页面到调用它的应用程序。对网络编程有一些研究的朋友会看出我这个设想很象SOAP,但是在这里我不想使用SOAP,因为如果那样的话会使程序变得很复杂。
想要改变一个完全独立的客户端单机版程序是不太现实的,但即使如此,从一个集中的服务器上下载应用程序设置也比使用本地的INI文件或Windows注册标有更大的独立性和灵活性。举例来说,假设你有一支手机销售队伍,他们需要访问集中化的信息来更有效的销售手机,每天,总公司集中收集数据,然后用电子邮件的形式发送给销售人员。然而,市场的压力和迅速变化的销售形式势必使销售人员要访问最新的数据信息。但是,网络管理员却坚持拒绝让在远程客户端的销售人员访问总公司数据库服务器,因为他们不想通过公用的网络发送用户名和登录密码。因此势必要使用一种新的技术代替基于客户端/服务器的技术,不要着急,我想看完本文你就会解决这个问题的。
让我们先分析一下客户端/服务器应用程序。在一个标准的客户端/服务器应用程序中,在应用程序开始时,你能够初始化数据库连接字符串,这就意味着,客户有使用数据库连接字符串的权利,这包括用户名和口令。但是客观情况如果不允许你在网络上发送这些信息的话,你就必需在不联接数据库的情况下直接从客户端取得数据发送给客户。那么解决方案之一就是在服务器上创建一个ASP页(在本例中称为getData.asp)接收特定格式的POST数据,它要求一个包含XML字符串,用来创建ADO对象并运行存储过程或动态SQL语句命令。如果信息有效的话,getData.asp执行存储过程,并返回一个XML格式的数据集、返回值列表或错误页面信息的XML字符串。对于返回数据的命令,客户端要么重新实例化要么返回值或使用XML DOM(Document Object Model文档对象模型)格式的错误页面。
好,下面就让我们来讨论一下如何实现这个页面吧!
getData.asp页面首先使用一个DOMDocument对象来保存客户端发送的数据:
'创建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
接着,应该让页面创建一个Command对象,读入所有<param>元素,并且为request中的每一个元素创建一个参数。最后,让页面打开一个连接使用存储过程adExecuteNoRecords选项来执行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
如果能够返回数据的话,那么returnData变量就为真值,并且把结果数据集返回到客户端,仍然是一个XML文档。
if returnsData then
R.Save Response, adPersistXML
if err.number <> 0 then
call responseError ("数据集发生存储错误" & "在命令'" & CommandText & "': " & Err.Description)
Response.end
end if
如果输出参数返回值,那么这个页面将返回一个包含这些值的XML字符串。文档的根元素是一个<values>标记,每一个返回值都有其相应的子元素,如果发生任何错误,页面都会格式化并返回一个包含错误信息的XML字符串:
Sub responseError(sDescription)
Response.Write "<response><data>错误: " & sDescription & "</data></response>"
Response.end
End Sub
假设在我们前面所说的例子中,我们想在应用程序中显示区域的左半边显示客户的姓名列表,再在每个客户姓名后面加上两个链接:Purchase History和Recent Purchase。当用户点击其中的一个链接,客户程序就会运行一个存储过程并在右边区域显示出结果。 为了显示这个想法的灵活性,我想让用于返回数据的三个操作单元执行不同的工作过程,它们都调用getData.asp。首先,通过调用CustOrderHist来运行一个存储过程,返回客户的Purchase History,它搜索Northwind数据库(为了方便起见我使用MS SQL中自带的数据库)并返回一个数据集。用于返回Recent Purchase 的查询语句运行一个叫RecentPurchaseByCustomerID的存储过程,来接收输入的CustomerID参数并通过ProductName参数返回最近顾客购买的商品名。定义其处理过程相应SQL语句如下:
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