DELPHI 6 搶先研究– BizSnap/SOAP/WebService 之二

來源:互聯網
上載者:User
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;interfaceUses    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;implementationInitialization    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;interfaceUses    InvokeRegistry, SvrDataType;Type    ISoapEmployee = Interface( IInvokable )        ['{31903B5A-96B3-43C2-A7B5-F67F6DB829E5}']        Function GetEmployee( aEmpNo : Integer ) : TEmpInfo; StdCall;    End;implementationInitialization    InvRegistry.RegisterInterface( TypeInfo( ISoapEmployee ) );end.

    此單元中定義了 SOAP 介面,這與前一個例子並沒有大的不同,只是這次為了清晰起見,將此介面放在一個單獨的單元裡實現。唯一區別較大的是此介面中的方法 GetEmployee 返回了一個自訂資料類型: TEmpInfo 。

7.在 SvrWMMain 單元中加入 SOAP 實作類別,完整的單元內容如下:

unit SvrWMMain;interfaceuses  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;implementationuses 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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.