发信人: fancynet() 
整理人: winsy(2003-03-09 13:23:08), 站内信件
 | 
 
 
用VB和XML建立集中式应用程序(上) 
 (作者:青苹果工作室编译 2001年03月19日 15:41) 
 
   很长一段时间以来,Visual Basic程序员都是在编写客户机/服务器程序,这些程序工作在私有网络上。但是这种在一台PC机上安装一个独立应用程序的时代正在很快地结束,客户机/服务器结构不再能满足需要,现在的大多数商用程序需要共享数据。  
 
 为什么要建立基于Intranet和Internet的应用程序  
   编写基于Intranet和Internet的应用程序或修改C/S结构的程序为工作于Intranet和Internet上的程序的原因至少有以下几点:  
 
   首先,每天都在增加的远程雇员需要访问公司的数据。  
 
   其次,通过集中应用程序里的数据能监控对它们的访问和使用。  
 
   第三,使用本文谈及的技术,在应用程序启动时从一个中心位置查找全局设置信息,就能方便地维护和更新这些设置,有助于最大程度地降低对桌面应用程序的更新。  
 
   第四,通过Web服务器而不是从远程客户端来进行数据库操作,可以避免通过网络传递数据库的登录口令。  
 
   最后,如果使用Internet Explorer,通过后台查询数据,就能避免在修改部分内容时重画整个页面。  
 
 右图是在Internet Explorer中看到的一个应用程序实例。图中,应用程序显示了Northwind数据库中的信息。左边是客户名列表和两个链接:购买历史情况和最近的购买情况。当用户点击链接时,数据在右边的Details栏里显示出来。这个应用程序使用DHTML显示Details中的数据而不用刷新整个页面,而且没有使用框架。  
 实现思路  
   创建基于HTTP的VB应用程序的关键是XML和XMLHTTPRequest对象,XMLHTTPRequest对象是Microsoft的XML语法分析器 (msxml.dll)的一部分,它让你能通过HTTP向远端服务器发送GET和POST请求。运行在远端服务器上的程序(我们将要使用的是 ASP页面,但应用程序可以使用任何服务器端脚本机制)接受请求、解释内容,并向调用它的应用程序返回数据或错误消息。你可能会觉得这和对SOAP的描述差不多,实际上也是如此,但这里我们不使用SOAP,因为它会使代码变得复杂。无论如何,重要的是:要理解这里所用的技术背后的思想,它和SOAP的思想是一样的,但不像SOAP那样复杂。  
 
   将不停改变的客户端应用程序完全隔离起来是不可能的,但通过从中心服务器而不是从本地INI文件或Windows注册表中来加载应用程序设置,就能在很高程度上建立应用程序的独立性。例如,假定有一个流动的销售团队,他们需要访问集中管理的数据以便进行有效的电话推销。过去,这些数据是集中采集的,每天通过电子邮件提供给销售人员。然而,市场的压力和快速变化的销售情况迫使销售人员必须能访问需要的最新数据。不幸的是,IT经理们坚决拒绝允许远程用户访问数据库服务器,因为他们不希望通过公用的Internet发送用户名和口令。不过不要担心,我们将采取方法使大家都满意。  
 
   在标准的客户机/服务器程序中,你可能需要在应用程序启动时初始化数据库连接串,这意味着客户机必须能访问连接串信息,包括用户名和口令。但是,当不允许通过网络传送那些信息时,就需要在客户端和数据库之间不建立直接连接的情况下获取数据。  
 
   解决办法是在服务器上建立一个ASP页面,在示例代码中它的名字是getData.asp,它接受特定形式的POST数据,它等待一个 XML字符串,这个字符串里包含着建立ADO Command对象并运行存储过程或动态SQL语句所需的命令信息。如果信息充分,getData.asp执行存储过程并返回一个XML字符串,这个字符串里包含以XML格式表述的Recordset、一系列返回值或一个错误消息。对于返回数据的命令,客户端或者将返回的Recordset对象重新表述,或者使用XML文档对象模型(DOM)来查找返回值或错误消息。  
 
 ASP代码和所需接收的数据  
   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 _  
 
    ("Could not load XML message." & _  
 
    "Description: " & _  
 
    xml.parseError.reason & _  
 
    "<br>Line: " & xml.parseError.Line)  
 
   End If  
 
   它查找元素commandtext的取值或元素returnsdata、returnsvalues的取值。因为查找这些元素取值的代码是相似的,因此这里只给出查找元素commandtext取值的代码:  
 
   Set N = xml.selectSingleNode _  
 
    ("command/commandtext")  
 
   If N Is Nothing Then  
 
    Call responseError _  
 
      ("Missing <sp_name> parameter.")  
 
   Else  
 
    sp_name = N.Text  
 
   End If  
 
   然后,页面创建一个Command对象,读取所有<param>元素,并为请求中存在的每一个元素创建一个参数,代码如下:  
 
    ' create parameters, if any  
 
   set nodes = xml.selectNodes("command/param")  
 
   if nodes is nothing then  
 
    ' no parameters  
 
   elseif nodes.length = 0 then  
 
    ' no parameters  
 
   else  
 
    for each param in nodes  
 
      ' Response.Write server.HTMLEncode(param.xml) & "<br>"  
 
      on error resume next  
 
      paramName = param.selectSingleNode("name").text  
 
      if err.number <> 0 then  
 
        call responseError("Parameter creation: " & _  
 
        "Unable to find the name " & "tag.")  
 
      end if  
 
      paramType = param.selectSingleNode("type").text  
 
      paramDirection = param.selectSingleNode("direction").text  
 
      paramSize = param.selectSingleNode("size").text  
 
      paramValue = param.selectSingleNode("value").text  
 
      if err.number <> 0 then  
 
        call responseError("The parameter named '" & _  
 
        paramName & "' was " & "missing one or more " & "required fields.")  
 
      end if  
 
      cm.Parameters.Append cm.CreateParameter(paramName,paramType, _  
 
      paramDirection,paramSize,paramValue)  
 
      if err.number <> 0 then  
 
        call responseError("Unable to create or " & "append the parameter " & _  
 
        "named '" & paramName & ".' " & err.description)  
 
        Response.end  
 
      end if  
 
    next  
 
    on error goto 0  
 
   end if  
 
   最后,页面打开一个链接并执行请求,对于存储过程,它使用不返回数据的adExecuteNoRecords选项。  
 
   set conn =  
 
    Server.CreateObject_  
 
    ("ADODB.Connection")  
 
   conn.Mode=adModeReadWrite  
 
   conn.open _  
 
    Application("ConnectionString")  
 
   set cm.ActiveConnection=conn  
 
   ' retrieve the data  
 
   if not returnsData then  
 
    cm.Execute  
 
   else  
 
    set R = server.CreateObject _  
 
      ("ADODB.Recordset")  
 
    R.CursorLocation = adUseClient  
 
    R.Open cm, ,adOpenStatic, _  
 
    adLockReadOnly  
 
   end if  
 
   如果命令返回数据,变量returnsData的值将为True,页面以XML文档方式将recordset的结果返回给客户机:  
 
   ' return the data, if required  
 
   if returnsData then  
 
    R.Save Response, adPersistXML  
 
    if err.number <> 0 then  
 
      call responseError _  
 
      ("Recordset Save Error " & _  
 
      "on command '" & CommandText & _  
 
      "': " & Err.Description)  
 
      Response.end  
 
    end if  
 
   ...  
 
 如果命令以输出参数返回值,页面将返回包含这些值的XML字符串。该文档的根是一个,每一个返回值对应一个子元素(参见本文可下载例程代码)。  
   如果产生错误,页面使用responseError子程序格式化并返回一个XML字符串。参数sDescription中包含了错误文本:  
 
   Sub responseError(sDescription)  
 
    Response.Write _  
 
      "<response><data>Error: " & sDescription & "</data></response>"  
 
    Response.end  
 
   End Sub  
 
   上面的图中显示了运行中的一个实例。应用程序在左边的<div>标记里显示一个客户名列表。每个客户旁边有两个链接:Purchase History和Recent Purchase。当用户点击其中之一时,客户端程序运行一个存储过程并在右边的<div>标记里显示结果。  
 
   为了显示这一方案的灵活性,虽然都使用到getData.asp,但三个返回数据的操作以互不相同的方式工作。对客户列表的查询涉及到动态SQL。对Purchase History的查询运行了一个称为CustOrderHist的存储过程,它附带在Northwind数据库中,并且返回一个recordset。对Recent Purchase的查询运行了一个称为RecentPurchaseByCustomerID的存储过程,它接受一个输入参数CustomerID并在一个输出参数ProductName中返回该客户最近购买的产品的名称。过程的定义是:  
 
   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  
 
   无论查询中包含的是动态SQL、返回recordset的存储过程、还是在输出参数中返回值的存储过程,设置POST消息的过程差不多都完全相同。客户页面创建XML字符串<command>、创建XMLHTTPRequest对象,用Open方法将它设置为对getData.asp 页面中的URL使用POST方法、并制定对象以异步方式工作(Open方法中的False参数)。它使用Send方法发送字符串:  
 
   set xhttp = createObject("msxml2.XMLHTTP")  
 
   xhttp.open "POST", "http://localhost/myWeb/getData.asp", False  
 
   xhttp.send s  
 
   注意:在Open方法中使用的URL必须是一个完整的URL,而不是一个相对URL。换句话说,下面的代码不能工作,因为URL是不完整的:  
 
   xhttp.open "POST", "getData.asp", False  
 ————转载自赛迪网
 
 
                                   
 
  ---- 北京网络工具(NetTool)斑竹
 Oicq:8762220 Icq:89170510
 E-Mail:[email protected]
                                | 
 
 
 |