CSDN博客

img Raptor
博客专家

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

发表于2001/10/27 0:35:00  4466人阅读

分类: Web/WebService

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

0 0

相关博文

我的热门文章

img
取 消
img