DELPHI 6 抢先研究-- BizSnap/SOAP/WebService 之二

类别:Delphi 点击:0 评论:0 推荐:
DELPHI 6 抢先研究-- BizSnap/SOAP/WebService 之二 -- 通过 SOAP 传递自定义类型数据

    在前一个例子(见 《DELPHI 6 抢先研究 -- BizSnap/SOAP/WebService 之一 -- 一个 Hello world! 的例子》)中我们看到,通过 SOAP 可以很方便地进行远程对象调用,虽然那个例子用的对象是一个 Delphi 类,但实际上只需要对对象作一个 SOAP 包装,即可调用包括 COM/CORBA/EJB 等各种对象(除 EJB 必须用 Java 实现外, COM/CORBA 都已可以用 Delphi 实现)。在那个例子中,接口方法用到的数据类型都是标准类型,但实际应用中常常会碰到要传递自定义类型的情况,这时的操作略麻烦一些,详情如李维 《樂趣無窮,可能無限的新技術-Web Service》 一文中的例子所示。
    同样,这里也要用一个例子来说明通过 SOAP 传递自定义数据类型的方法,这个例子会是一个比较麻烦的例子:

    服务端:
1.New|WebServices|Soap Server Application ,如下图:

这个例子是用 Web App Debugger (详见《DELPHI 6 抢先研究 -- Web 应用开发及调试》), 设置其 CoClass Name 为 wadSoapDemo2 , 如下图:

2.SaveAll , Unit2 命名为: SvrWMMain , Unit1 不改名, Project1 命名为: Server ;
3.New|Data Module ,将此单元保存为 SvrDataMod ;
4.在其中放入两个 dbExpress 控件: SQLConnection1 和 SQLDataSet1 ,如下图:

其属性设置为:

SQLConnection1 ConnectionName := IBLocal;
LoginPrompt := false;
Params.Values['Database'] := '[...]\Examples\Database\Employee.gdb';
// 上面的 [...] 为你的 InterBase 安装路径 SQLDataSet1 SQLConnection := SQLConnection1;
CommandText := 'select FULL_NAME, PHONE_EXT from EMPLOYEE WHERE EMP_NO = :EMP_NO';

5.New|Unit ,将此单元保存为 SvrDataType ,其内容如下:

unit SvrDataType; interface Uses InvokeRegistry; Type TEmpInfo = Class( TRemotable ) Private FName : String; FPhone : String; published Property Name : String Read FName Write FName; Property Phone : String Read FPhone Write FPhone; end; implementation Initialization RemClassRegistry.RegisterXSClass( TEmpInfo ); Finalization RemClassRegistry.UnRegisterXSClass( TEmpInfo ); end.

    此单元中定义了类: TEmpInfo ,用于记录员工信息,包括 Name 和 Phone 两个域,均为字符串类型。对于需要传递到客户端的数据类型,必须从 TRemotable 类派生,它能够自动处理类型信息的传递。如果要手工处理自定义数据类型的传递,则必须从 TRemotableXS 类派生,其用法与 TRemotable 类似,但这样的话,必须实现两个转换方法: NativeToXS 和 XSToNative ,详见 Delphi6\Source\Soap\XSBuiltIns.pas 中的几个类的实现。
    需要注意的是,此类中将两个属性放在 Published 中,这里一定要这么做,我曾经因为将它们放在了 Public 中,导致客户端无法取得服务端的数据类型信息,后来才发现它们必须放在 Published 中才行,所以虽然这里并不是控件,这些属性也不是为了要在 Object Inspector 中显示,但仍然需要放在 Published 中。这可能是因为 Published 较 Public 多一些 RTTI(Run Time Type Info,运行时类型信息) 的东东,而远程数据类型是依赖于 RTTI 的。
    最后是在远程类注册信息库中注册和反注册此类。

6.New|Unit ,将此单元保存为 SvrSoapIntf ,其内容如下:

unit SvrSoapIntf; interface Uses InvokeRegistry, SvrDataType; Type ISoapEmployee = Interface( IInvokable ) ['{31903B5A-96B3-43C2-A7B5-F67F6DB829E5}'] Function GetEmployee( aEmpNo : Integer ) : TEmpInfo; StdCall; End; implementation Initialization InvRegistry.RegisterInterface( TypeInfo( ISoapEmployee ) ); end.

    此单元中定义了 SOAP 接口,这与前一个例子并没有大的不同,只是这次为了清晰起见,将此接口放在一个单独的单元里实现。唯一区别较大的是此接口中的方法 GetEmployee 返回了一个自定义数据类型: TEmpInfo 。

7.在 SvrWMMain 单元中加入 SOAP 实现类,完整的单元内容如下:

unit SvrWMMain; interface uses SysUtils, Classes, HTTPApp, WSDLPub, SOAPPasInv, SOAPHTTPPasInv, SoapHTTPDisp, WebBrokerSOAP; type TWebModule2 = class(TWebModule) HTTPSoapDispatcher1: THTTPSoapDispatcher; HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker; WSDLHTMLPublish1: TWSDLHTMLPublish; private { Private declarations } public { Public declarations } end; var WebModule2: TWebModule2; implementation uses WebReq, InvokeRegistry, SvrDataType, SvrSoapIntf, SvrDataMod; {$R *.DFM} Type TSoapEmployee = class( TInvokableClass, ISoapEmployee ) Protected Function GetEmployee( aEmpNo : Integer ) : TEmpInfo; StdCall; End; { TSoapEmployee } Function TSoapEmployee.GetEmployee(aEmpNo: Integer): TEmpInfo; StdCall; Begin Result := TEmpInfo.Create; If ( Not Assigned( DataModule2 ) ) Then DataModule2 := TDataModule2.Create( Nil ); Try DataModule2.SQLConnection1.Open; With DataModule2.SQLDataSet1 Do Begin ParamByName( 'EMP_NO' ).AsInteger := aEmpNo; Open; If ( Not Eof ) Then Begin Result.Name := FieldByName( 'FULL_NAME' ).AsString; Result.Phone := FieldByName( 'PHONE_EXT' ).AsString; End Else Begin Result.Name := ''; Result.Phone := ''; End; Close; End; DataModule2.SQLConnection1.Close; Finally DataModule2.Free; DataModule2 := Nil; End; End; initialization WebRequestHandler.WebModuleClass := TWebModule2; InvRegistry.RegisterInvokableClass( TSoapEmployee ); end.

    这里接口的实现类 TSoapEmployee 的定义与实现与前一例子类似。 GetEmployee 的实现也不复杂:首先,如果未创建 DataModule2 的实例(需要在 Project|Options 中将 DataModule2 从自动创建列表中移去)则创建一个 DataModule2 的实例;然后连接到数据库,查询指定员工号的员工信息;最后返回此信息。注意:这里用了 dbExpress ,有些地方与 BDE/ADO 不太一样,如不能使用 RecordCount ,只能用 Eof 来判断是否有查询结果。

8.至此完成服务端的全部程序,编译并运行,然后退出即完成 Web App Debugger 应用程序的注册。
启动 Web App Debugger ,再启动浏览器,在地址栏输入: http://localhost:1024/Server.wadSoapDemo2/wsdl/ISoapEmployee 即可浏览其 WSDL 内容,在其中包含了自定义类型的必要信息,但如果前面 SvrDataType 单元中的 TEmpInfo 类的属性不是放在 Published 部分的话,这里将看不到类型信息。下面是这个 WSDL 中的 types 标记部分内容:

<types> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:SvrDataType"> <xs:complexType name="TEmpInfo"> <xs:sequence> <xs:element name="Name" type="xs:string"/> <xs:element name="Phone" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:schema> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:WSDLSoap"> <xs:complexType name="TWSDLSOAPPort"> <xs:sequence> <xs:element name="PortName" type="xs:string"/> <xs:element name="Addresses" type="ns3:TWideStringDynArray"/> </xs:sequence> </xs:complexType> </xs:schema> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:Types"> <xs:complexType name="TWideStringDynArray"> <xs:complexContent> <xs:restriction base="soapenc:Array"> <xs:sequence/> <xs:attribute ref="soapenc:arrayType" n1:arrayType="xs:string[]" xmlns:n1="http://schemas.xmlsoap.org/wsdl/"/> </xs:restriction> </xs:complexContent> </xs:complexType> </xs:schema> </types>

    从上面这一段 WSDL 中可以看出服务端导出了三个“复杂类型” -- complexType : TEmpInfo, TWSDLSOAPPort, TWideStringDynArray ,其中除了 TEmpInfo 是我们自己定义的数据类型以个,另两个是 Delphi 内部定义使用的类型,在客户端导入 WSDL 时我们会再看到它们的。

    再来看客户端的实现:
1.New|Application 新建一个普通的 VCL 应用程序;
2.SaveAll , Unit1 命名为 ClnMain , Project1 命名为 Client ;
3.在 Form1 上放上 HTTPRIO1, Edit1, Button1, Label1, Label2 等控件,如下图:

其中 Edit1 的 Text 设置为 1 , Button1 的 Caption 设置为 GetEmployee , HTTPRIO1 的 URL 属性设置为: http://localhost:1024/Server.wadSoapDemo2/soap ;
4.New|Web Services|Web Services Importer ,与前一例子相似,只是导入的 URL 改为: http://localhost:1024/Server.wadSoapDemo2/wsdl/ISoapEmployee ;
5.如果服务端的 WSDL 如前面所述的那样,则将导入三个单元,分别包含了 TWSDLSOAPPort、 TEmpInfo、 ISoapEmployee ,其中 ISoapEmployee 是我们所认识的 SOAP 接口单元, TEmpInfo 是我们在服务端定义的数据类型, TWSDLSOAPPort 是 Delphi 内部定义的一个数据类型,我们曾在服务端的 WSDL 中看到过这个类型。 Save All ,将 TWSDLSOAPPort 的单元保存为 ClnSoapPort ,将 TEmpInfo 保存为 ClnDataType ,将 ISoapEmployee 保存为 ClnSoapIntf 。注意要将 ClnSoapIntf 单元中的 Uses 中的两个名为 UnitN 的单元相应改为 ClnSoapPort 和 ClnDataType 。由于这三个单元的内容都不需要改变,只要服务端是正确的,可以不必了解这三个单元的内容(特别是 ClnSoapIntf 和 ClnDataType 与服务端的相应单元基本相同),所以这里也就不列出它们的内容了。
6.双击 Button1 输入下面的代码:

procedure TForm2.Button1Click(Sender: TObject); Var ei : TEmpInfo; begin ei := ( HTTPRIO1 As ISoapEmployee ).GetEmployee( StrToInt( Edit1.Text ) ); If ( Assigned( ei ) ) Then Begin Label1.Caption := ei.Name; Label2.Caption := ei.Phone; End; end;

7.编译运行,在 Edit1 中输入"1"或其它数据库中没有相应记录的员工号,按 Button1 , Label1 和 Label2 都将显示空;输入"2"或其它数据库中有记录的员工号,则将在 Label1 中显示员工全名,在 Label2 中显示此员工的电话号码,如下图:

    做过一遍再看这个例子也不是那么复杂的。

猛禽 Jun.20-01, Oct.20, Oct.24

本文地址:http://com.8s8s.com/it/it5823.htm