Archive for category Delphi XE程式設計

使用DataSnap XE開發分散式, 精簡型(Thin Client) 應用系統研討會投影片和範例

上星期在台北, 新竹和高雄做了3場有關DataSnap XE技術的研討會, 回台北之後就感冒了, 晚了這麼多天才把研討會投的影片和範例放上來實在很抱歉.

由於WordPress似乎不能上傳Rar的檔案形態, 因此我把範例壓縮檔再加上.doc的副檔名, 需要的朋友下載之後再把.doc副檔名從檔案名稱移除即可.

此外本文附的範例包含了DataSnap證證客戶端的範例程式, 興德網上的範例缺少了這個範例.

這次研討會之後我可能會休息一斷時間, 不過看到這次的3場研討會參加的人數都比以前多上許多, 反應也很熱烈, 許多參加的朋友對於Delphi/BCB未來的GUI框架和Delphi 64位元版本都非常的期待, 這真是一個很好的現象, 我個人也希望EMBT好好的把握下一個版本的質量, 推出一個令Delphi/BCB使用者都振奮不已的版本. 在此同時我也靜待EMBT讓我加入Beta的測試.

DataSnap Thin Client Development

Demos.rar

 

9 則迴響

進階DataSnap回叫功能2 – 不同用戶端藉由回叫功能溝通

雖然一個用戶端應用程式可以藉由註冊一個回叫通道並且在回叫通道中註冊多個回叫ID,但另外一個非常實用的回叫功能就是如何讓不同的用戶端能夠藉由回叫來相互溝通。DataSnap XE的回叫功能也支援不同的用戶端藉由回叫機制來相互傳遞任何的資料,在說明如何實作之前,讓我們總結一下DataSnap框架回叫機制可由用戶端註冊變動的部份包含了:

  • 通道名稱
  • 用戶端識別
  • 回叫識別

因此要讓不同的用戶端能夠藉由回叫功能互動,那麼用戶端只要掌握上述的三個變動的即可。例如用戶端1要和用戶端2藉由回叫互動,用戶端1只需要知道用戶端2存在的通道名稱,用戶端2的用戶端識別以及用戶端2的回叫識別,那麼用戶端1就可以回叫用戶端2來傳遞任何資訊了。

現在就讓我們看看如何實作不同用戶端藉由回叫功能相互溝通。

修改DataSnap伺服器

回到範例回叫專案群組,開啟範例DataSnap伺服器的主表單,在主表單上方加入一個TListBox以顯示所有用戶端識別資訊,另外再加入兩個TButton,分別是『列出所有客戶端識別』以及『清除所有客戶端識別』,如下圖所示:

要取得所有連結到DataSnap伺服器的用戶端識別資訊,我們可以呼叫TDSServer元件的GetAllChannelClientId方法,GetAllChannelClientId能夠回傳在一個特定的回叫通道中所有註冊的用戶端識別,它的原型如下:

function GetAllChannelClientId(const ChannelName: String): TList<String>;

我們只需要傳遞特定的回叫通道給GetAllChannelClientId即可從回傳的TList物件中取得所有註冊的用戶端識別,在這個範例中我們只需要傳入DEMOChannel即可。因此『列出所有客戶端識別』按鈕的OnClick事件處理函式實作如下:

procedure TForm17.btnListAllClientIdsClick(Sender: TObject);

var

aIdList : TList<String>;

sId : String;

begin

aIdList := ServerContainer5.DSServer1.GetAllChannelClientId(DEMOChannel);

try

for sId in aIdList do

lbAllClientIds.Items.Add(sId);

finally

aIdList.Free;

end;

end;

現在就可以修改範例DataSnap用戶端應用程式了。

修改DataSnap用戶端應用程式

開啟範例DataSnap用戶端應用程式的主表單,在其中加入兩個TEdit元件以便讓使用者輸入這個用戶端的用戶端識別以及回叫識別,再置入一個『回叫客戶端』按鈕以及一個TMemo元件,當我們在新的TMemo元件中輸入任何資訊時,再點選『回叫客戶端』按鈕就可以把新的TMemo元件中的資訊藉由回叫功能傳遞給另外一個用戶端應用程式。但我們如何指定要回叫那一個用戶端應用程式呢?這就由新加入的兩個TComboBox來指定了,我們可以在這兩個TComboBox中輸入要溝通的另外一個用戶端的用戶端識別以及回叫識別,那麼『回叫客戶端』按鈕的OnClick事件處理函式就可以藉由回叫功能來回叫另外一個用戶端應用程式了,此時範例用戶端應用程式的主表單如下所示:

接下來就解決的問題是一個用戶端應用程式如何能夠回叫另外一個用戶端應用程式呢? 這可以使用TDSAdminClient類別的NotifyCallback方法。TDSAdminClient是位於DSProxy程式單元中的類別,在XE版中因應強化的回叫功能而加入了數個新的方法,其中的NotifyCallback方法可以回叫指定回叫通道中特定用戶端識別以及回叫識別的用戶端,它的原型宣告如下:

function NotifyCallback(ChannelName: string; ClientId: string; CallbackId: string; Msg: TJSONValue; out Response: TJSONValue): Boolean;

NotifyCallback接受數個參數,下面的表格說明了每一個參數的目的:

參數名稱 說明
ChannelName 回叫通道名稱
ClientId 用戶端識別
CallbackId 回叫識別
Msg 傳遞的資訊
Response 被回叫用戶端的回傳資訊

因此我們只需要為每一個用戶端應用程式在註冊時加入一個獨特的用戶端識別,如此一來我們就可以藉由NotifyCallback讓任何兩個用戶端或是多個用戶端相互溝通了。那麼我們如何設定用戶端識別? 還記得我們在用戶端應用程式中使用了TDSClientCallbackChannelManager元件來註冊用戶端的回叫資訊嗎? TDSClientCallbackChannelManager擁有一個ManagerId特性,這個ManagerId特性就可以做為用戶端的識別,因此讓我們修改用戶端中『啟動回叫功能』按鈕的OnClick事件處理函式如下:

001    procedure TfmMainForm.btnStartClick(Sender: TObject);

002    begin

003      SetupTask;

004      AddIdsToComboBox(edtClientId.Text, edtCallbackId.Text);

005      EnableDisableButtons(False, True, True);

006      DemoChannelManager.ManagerId := edtClientId.Text;

007      DemoChannelManager.RegisterCallback(edtCallbackId.Text, TDemoCallback.Create);

008      aIdList.Add(edtCallbackId.Text);

009    end;

在004行我們把使用者於edtClientId元件輸入的用戶端識別加入到cbClientIds中,並且把使用者於edtCallbackId元件輸入的回叫識別加入到cbCallbackIds中:

procedure TfmMainForm.AddIdsToComboBox(aClientId, aCallbackId: String);

begin

cbClientIds.Items.Add(aClientId);

cbCallbackIds.Items.Add(aCallbackId);

end;

接著在006行設定TDSClientCallbackChannelManager元件的ManagerId特性為使用者於edtClientId元件輸入的用戶端識別,最後才於007行呼叫RegisterCallback註冊這個用戶端的回叫資訊。一旦設定了用戶端識別,其他用戶端就可以使用這個用戶端識別來和這個用戶端進行溝通。

完成了上述的修改之後,我們就可以實作主表單中『回叫客戶端』按鈕的OnClick事件處理函式了:

001    procedure TfmMainForm.btnBroadcastToClientClick(Sender: TObject);

002    var

003      LClient: TDSAdminClient;

004      LMessage: TJSONString;

005      LResponse: TJSONValue;

006      LConnection: TDBXConnection;

007    begin

008      LConnection := scnnCallbackServer.DBXConnection;

009      LClient := TDSAdminClient.Create(LConnection, False);

010      try

011        LMessage := TJSONString.Create(Format(‘呼叫通道: %s, 回叫識別: %s, 客戶端識別: %s, 回叫訊息: %s’,

012          [DemoChannelManager.ChannelName, cbCallbackIds.Text, cbClientIds.Text, mmChannelCallbacks.Text]));

013        try

014          LClient.NotifyCallback(DemoChannelManager.ChannelName, cbClientIds.Text, cbCallbackIds.Text, LMessage, LResponse);

015          try

016            if LResponse <> nil then

017              edtResponse.Text := Format(‘客戶端回應: %s’, [LResponse.ToString])

018            else

019              edtResponse.Text := Format(‘客戶端回應: %s’, [‘nil’]);

020          finally

021            LResponse.Free;

022          end;

023        finally

024          LMessage.Free;

025        end;

026      finally

027        LClient.Free;

028      end;

029    end;

首先我們在009行先建立TDSAdminClient物件,在010行建立要傳遞給其他用戶端應用程式的以TJSONString封裝的訊息,接著在014行就藉由NotifyCallback方法呼叫其他的用戶端,請注意我們傳入的資訊,首先參數是回叫通道名稱,這可以從TDSClientCallbackChannelManager元件的 ChannelName特性值取得,第2個參數是用戶端識別,我們傳入主表單中由使用者在TComboBox元件cbClientIds的Text特性值,第3個參數是用戶端回叫識別,我們傳入主表單中由使用者在TComboBox元件cbCallbackIds的Text特性值,第4個參數是這個用戶端要傳遞給其他用戶端的資訊,我們傳入在011行封裝的TJSONString物件LMessage,最後一個參數則必須傳入型態為TJSONValue的物件以接受其他用戶端回傳的執行結果。

現在請編譯並且執行2份不同的範例用戶端應用程式,如下所示:

讓我們在第一個用戶端應用程式輸入其用戶端識別為『客戶端識別1』,其回叫識別為『回叫識別1』,接著在第二個用戶端應用程式中輸入其用戶端識別為『客戶端識別2』,其回叫識別為『回叫識別2』。之後我們如果點選範例DataSnap伺服器中來『列出所有客戶端識別』按鈕和『列出所有回叫識別』按鈕,那麼就可以如上圖左上方看到,範例DataSnap伺服器果然可在DemoChannel這個回叫通道中找到這些資訊。

現在請在用戶端應用程式1中的兩個TComboBox元件輸入『客戶端識別2』和『回叫識別2』讓用戶端應用程式1回叫用戶端應用程式2。同樣的請在請在用戶端應用程式2中的兩個TComboBox元件輸入『客戶端識別1』和『回叫識別1』讓用戶端應用程式1回叫用戶端應用程式1。

之後在用戶端應用程式1中下方的TMemo元件中輸入任何的資訊,並且點選『回叫客戶端』按鈕,那麼我們立刻可以在用戶端應用程式2的上方TMemo元件中看到由用戶端應用程式1中傳遞來的資訊。同樣的,如果我們在用戶端應用程式2中下方的TMemo元件中輸入任何的資訊,並且點選『回叫客戶端』按鈕,那麼我們立刻可以在用戶端應用程式1的上方TMemo元件中看到由用戶端應用程式2中傳遞來的資訊,此時這兩個用戶端應用程式看起來如下所示:

我們可以看到藉由DataSnap的回叫功能,我們果然可以讓不同的用戶端應用程式都藉由回叫通道來溝通並且相互傳遞任何的資訊。此外請注意上圖中兩個用戶端都相互回傳『客戶端回應:true』的訊息,這是因為我們在前面實作用戶端的回叫函式時,用戶端的回叫函式都回傳TJSONTrue物件。由於NotifyCallback可以回傳回叫用戶端的執行結果,因此如果我們希望回叫函式回傳其他型態的資訊,那麼我們可以修改兩個範例用戶端回叫函式如下:

001    function TDemoCallback.Execute(const Arg: TJSONValue): TJSONValue;

002    var

003      sDemoMessage : String;

004    begin

005    //  Result := TJSONTrue.Create;

006      Result := TJSONString.Create(‘成功呼叫客戶端’);

007

008      if (Arg is TJSONString) then

009      begin

010        sDemoMessage := TJSONString(Arg).Value;

011        TThread.Synchronize(nil,

012                            procedure

013                            begin

014                              fmMainForm.mmDemoMessage.Lines.Text := sDemoMessage;

015                            end

016                            );

017      end;

018    end;

019

020    { TDemoCallback2 }

021

022    constructor TDemoCallback2.Create;

023    begin

024

025    end;

026

027    function TDemoCallback2.Execute(const Arg: TJSONValue): TJSONValue;

028    var

029      sDemoMessage : String;

030    begin

031    //  Result := TJSONTrue.Create;

032      Result := TJSONString.Create(‘成功呼叫客戶端’);

033

034      if (Arg is TJSONString) then

035      begin

036        sDemoMessage := TJSONString(Arg).Value;

037        TThread.Synchronize(nil,

038                            procedure

039                            begin

040                              fmMainForm.mmDemoMessage2.Lines.Text := sDemoMessage;

041                            end

042                            );

043      end;

044 end;

在上面的兩個回叫函式中我們修改回傳TJSONString封裝的資訊而不是單純的TJSONTrue物件,這是果然是因為NotifyCallback回傳的型態是TJSONValue,因此任何從TJSONValue類別繼承下來的類別物件都可以做為回叫函式的執行回傳結果。

現在如果我們再次執行兩個用戶端應用程式,那麼就可以看到類似如下的結果:

從上圖中我們可以看到不同的用戶端果然可以藉由回叫函式傳遞任何的資訊回其他的用戶端應用程式。

現在您應該已經充分的瞭解了DataSnap的回叫功能了,也許您可以自己試著完成一個小功課,請繼續修改範例DataSnap伺服器和用戶端應用程式讓用戶端應用程式可以註冊不同的回叫通道而不像前面的範例應用程式只使用固定的DemoChannel這個回叫通道,如何? 應該不算太難而且應該很有趣吧。

 

DataSnap XE回叫功能最有意思的是還可以使用於瀏覽器型態的應用程式以開發輕薄型回叫DataSnap用戶端, 這是更深入的回叫機制了, 我們有機會再談吧.

Have Fun!

4 則迴響

進階DataSnap回叫功能

Delphi/C++Builder 2010加入了回叫機制, 我也在2009年的部落格文章中說明了如何使用Delphi/C++Builder 2010的回叫功. 然而Delphi/C++Builder XE又再次強化了回叫機制, 讓這個功能更為強大和完善, 由於XE版的回叫機制提供了眾多新的功能, 因此我想藉由這篇文章說明一下如何使用XE版的回叫機制, 雖然我無法一次把所有的功能寫完, 但起個頭還是很有用的.

DataSnap XE在原有的基礎回叫機制之上加入了許多強大的新功能,從DataSnap XE開始開發人員可以使用下面的回叫功能:

  • 用戶端可向伺服端註冊回叫通道,如此一來伺服器可以一次回叫所有在同回叫通道中所有註冊的用戶端回叫函式
  • 用戶端可以同時註冊多個不同的回叫通道
  • 用戶端可以藉由回叫通道呼叫不同的用戶端
  • 新增回叫元件以幫助開發人員簡化開發回叫機制

DataSnap XE新的回叫功能雖然很多,但使用起來仍然相當的容易,下面說明了如何使用這些DataSnap XE新的回叫功能的基本步驟:

  • 用戶端使用TDSClientCallbackChannelManager向伺服器註冊一個回叫通道
  • 伺服器使用TDSServer元件的BroadcastMessage方法回叫所有註冊的用戶端

當然,開發人員可以更進一步的使用DataSnap XE進階的回叫功能,不過在那之前也許我們應該先說明數個範例讓讀者瞭解如何使用這些基本的步驟。

開發回叫DataSnap伺服器

在Delphi整合發展環境中建立一個DataSnap Server專案:

在設定此伺服器的特性時,讓我們目前只選擇使用TCP/IP通訊協定,如下圖所示:

開啟專案中的ServerContainer程式單元,此時在ServerContainer中產生了兩個元件,TDSServer以及TDSTCPServerTransport,由於接下來我們將先展示Windows用戶端的回叫功能,因此現在使用TCP/IP通訊協定就足夠了。

現在開啟專案主表單,並且在其中置入如下的元件:

主表格上方使用了TListBox元件,它可以顯示所有用戶端註冊的回叫識別ID,而下方的TMemo元件則是使用來回叫註冊用戶端,在稍後我們將在此TMemo元件中輸入資訊,這些輸入的資訊就會藉由回叫通道自動傳遞給用戶端。

DataSnap伺服器要回叫所有註冊的用戶端是非常容易的,只需要藉由TDSServer類別定義的BroadcastMessage方法即可,TDSServer類別中定義了兩個BroadcastMessage方法原型如下:

function BroadcastMessage(const ChannelName: String; const Msg: TJSONValue; const ArgType: Integer = TDBXCallback.ArgJson): boolean; overload;

 

function BroadcastMessage(const ChannelName: String; const CallbackId: String; const Msg: TJSONValue; const ArgType: Integer = TDBXCallback.ArgJson): boolean; overload;

這兩個BroadcastMessage方法的差異在於上述第一個BroadcastMessage可以傳遞訊息給它第一個參數ChannelName指定的通道中所有的回叫用戶端,而第二個BroadcastMessage方法則是只傳遞訊息給它第一個參數ChannelName指定的通道中由第二個參數CallbackId指定的回叫用戶端,最後這個兩個BroadcastMessage方法傳遞給用戶端的訊息則由第二個參數Msg封裝。

瞭解了如何使用BroadcastMessage方法之後,我們就可以看看如何把DataSnap伺服器中於TMemo元件中輸入的訊息傳遞給回叫用戶端。現在為主表單中的TMemo元件實作OnChange事件處理函式如下:

001    procedure TForm17.mmMessageChange(Sender: TObject);

002    var

003      vMessage : TJSONString;

004    begin

005      vMessage := TJSONString.Create(mmMessage.Lines.Text);

006      ServerContainer5.DSServer1.BroadcastMessage(DEMOChannel, vMessage);

007    end;

在005行我們把輸入於TMemo(mmMessage)中的資訊以TJSONString物件封裝,然後在006行藉由呼叫ServerContainer中的TDSServer元件的BroadcastMessage方法傳遞給所有註冊的用戶端。但誰是註冊的用戶端呢?請看BroadcastMessage的第一個參數DEMOChannel,這代表DataSnap伺服器會傳遞資訊給所有在DEMOChannel通道中註冊的用戶端。而DEMOChannel是一個通道的名稱,我們在DataSnap伺服器中定義它如下:

const

DEMOChannel = ‘DemoChannel’;

因此用戶端只要使用這個名稱向伺服器註冊回叫通道的話,就可以讓DataSnap伺服器回叫用戶端,當然也用戶端可以先向伺服器查詢已經定義在DataSnap伺服器中的回叫通道名稱,或是由用戶端自行在DataSnap伺服器中建立指定名稱的回叫通道。

由於回叫用戶端是向DataSnap伺服器中指定名稱的回叫通道註冊,而且每一個用戶端都使用一個特定的回叫識別ID來代表,因此我們也可以藉由TDSServer元件來查詢某一個名稱的回叫通道中所有註冊的用戶端回叫識別ID。

此範例DataSnap伺服器主表單中的『列出所有回叫識別』按鈕的OnClick事件處理函式就可以在主表單上方的TListBox中列出特定回叫通道中所有的用戶端回叫識別ID,下面就是它的OnClick實作程式碼:

001    procedure TForm17.Button1Click(Sender: TObject);

002    var

003      aIdList : TList<String>;

004      sId : String;

005    begin

006      aIdList := ServerContainer5.DSServer1.GetAllChannelCallbackId(DEMOChannel);

007      try

008        for sId in aIdList do

009          ListBox1.Items.Add(sId);

010      finally

011        aIdList.Free;

012      end

013    end;

GetAllChannelCallbackId方法會回傳TList<String>型態的執行結果,其中即包含了所有在此通道名稱中註冊的用戶端回叫識別ID,因此在006行藉由TDSServer元件呼叫GetAllChannelCallbackId之後,就可以取得到在DEMOChannel中註冊的識別ID,接著從008行到012行就把這些識別ID顯示在主表單的TListBox中。

現在請編譯並且執行此範例DataSnap伺服器,接著我們就可以實作回叫用戶端了,首先讓我們先說明如何建立Windows應用程式型態的用戶端,接著再說明如何建立瀏覽器型態的用戶端。

開發回叫DataSnap用戶端

在專案管理員中再建立一個VCL Form應用程式專案,並且在其中放入TSQLConnection,TDSClientCallbackChannelManager,TMemo,兩個TButton元件,如下圖所示。其中TSQLConnection是連結上一小節的範例DataSnap伺服器,而TDSClientCallbackChannelManager則是使用來向DataSnap伺服器註冊回叫用戶端的元件。

接著設定TDSClientCallbackChannelManager特性值如下:

特性名稱 特性值
ChannelName DemoChannel
CommunicationProtocol tcp/ip
DSHostname localhost
DSPort 211
Name DemoChannelManager

設定TDSClientCallbackChannelManager的ChannelName特性值為DemoChannel是因為前面範例DataSnap伺服器使用的通道名稱就是DemoChannel,而且前面範例DataSnap伺服器是支援TCP/IP通訊協定和使用211通信埠,因此我們需要設定TDSClientCallbackChannelManager相對應的特性值,下圖顯示了在物件檢視器中設定TDSClientCallbackChannelManager元件的特性值:

設定好TDSClientCallbackChannelManager元件之後,我們就可以看看用戶端主表單中的『啟動回叫功能』按鈕的實作程式碼了:

001    procedure TfmMainForm.btnStartClick(Sender: TObject);

002    begin

003      SetupTask;

004      EnableDisableButtons(False, True);

005      DemoChannelManager.RegisterCallback(callbackId, TDemoCallback.Create)

006    end;

007

008    procedure TfmMainForm.SetupTask;

009    begin

010      if not scnnCallbackServer.Connected then

011      begin

012        scnnCallbackServer.Connected := True;

013      end;

014      callbackId := DateTimeToStr(Now);

015      Self.Caption := callbackId;

016    end;

在btnStartClick事件處理函式中先於003行呼叫SetupTask方法以開啟TSQLConnection元件連結範例DataSnap伺服器,並且在014行根據目前的時間建立一個獨特的識別ID,callbackId。最後在005行呼叫TDSClientCallbackChannelManager元件的RegisterCallback方法向範例DataSnap伺服器註冊這個回叫用戶端。

TDSClientCallbackChannelManager元件的RegisterCallback方法原型定義如下:

function RegisterCallback(const CallbackId: String; const Callback: TDBXCallback): boolean; overload;

RegisterCallback接受兩個參數,第一個是每一個回叫用戶端的識別ID,第二個參數則是型態為TDBXCallback的物件,在前面的小節中我們已經解釋過TDBXCallback類別,因此在這裡我們需要建立一個從TDBXCallback衍生類別的物件,在這個衍生類別中我們需要複載虛擬方法Execute,如此一來範例DataSnap伺服器就可以藉由呼叫複載虛的擬方法Execute來呼叫到用戶端。

由於我們在上面的005行是傳遞TDemoCallback物件給RegisterCallback方法,因此我們需要在這個範例用戶端應用程式中定義和實作TDemoCallback類別,它需要從TDBXCallback繼承下來:

type

TDemoCallback = class(TDBXCallback)

public

constructor Create;

function Execute(const Arg: TJSONValue): TJSONValue; override;

end;

TDemoCallback需要實作虛擬方法Execute,當DataSnap伺服器回叫用戶端時就會執行虛擬方法Execute。由於在這個範例中我們希望範例DataSnap伺服器在TMemo中輸入的資訊能夠立刻顯示在用戶端,因此我們實作虛擬方法Execute如下:

001    function TDemoCallback.Execute(const Arg: TJSONValue): TJSONValue;

002    var

003      sDemoMessage : String;

004    begin

005      Result := TJSONTrue.Create;

006

007      if (Arg is TJSONString) then

008      begin

009        sDemoMessage := TJSONString(Arg).Value;

010        TThread.Synchronize(nil,

011                            procedure

012                            begin

013                              fmMainForm.mmDemoMessage.Lines.Text := sDemoMessage;

014                            end

015                            );

016      end;

017    end;

當DataSnap伺服器藉由回叫功能呼叫用戶端複載的Execute時,DataSnap伺服器可以把伺服端傳遞到用戶端的資訊封裝在Execute的參數Arg中。由於在前面的範例DataSnap伺服器是把在伺服端TMemo中的資訊封裝成TJSONString傳遞到用戶端,因此在007行先判斷伺服端傳遞來的是否是TJSONString型態,如果是的話,就00行取出伺服端傳遞來的資訊,接著由於我們需要把這個資訊顯示在用戶端主表單中的TMemo元件中,因此我們藉由呼叫TThread類別的類別方法Synchronize來更新用戶端的使用者介面(這是因為用戶端使用者介面的主執行緒和在背景的回叫執行緒是不同的,因此需要使用Synchronize來讓背景回叫執行緒更新主執行緒中的元件)。由於Synchronize定義了如下的原型:

class procedure TThread.Synchronize(AThread: TThread; AMethod: TThreadMethod);

因此在上面的010行中我們直接使用匿名方法來更新主表單的TMemo元件。

最後當我們不再需要讓用戶端被回叫時,可以呼叫TDSClientCallbackChannelManager元件的UnregisterCallback方法並且傳遞用戶端識別ID,例如下面的程式碼就是主表單中『停止回叫功能』按鈕OnClick事件處理函式的實作程式碼:

procedure TfmMainForm.btnStopClick(Sender: TObject);

begin

EnableDisableButtons(True ,False);

DemoChannelManager.UnregisterCallback(callbackId);

end;

現在編譯並且執行此範例回叫用戶端,從下圖我們可以看到,點選範例DataSnap伺服器中的『列出所有回叫識別』按鈕就可以在上方的TListBox中列出用戶端傳遞來的識別ID,而且只要用戶端應用程式點選了主表單中的『啟動回叫功能』按鈕註冊回叫用戶端,那麼我們在範例DataSnap伺服器的TMemo中輸入的任何資訊就會立刻顯示在用戶端應用程式的TMemo元件中,證明了回叫功能是成功的。

當然,用戶端可以註冊多個不同的回叫通道,在同一個回叫通道中也可以註冊多個識別ID,例如讓我們修改剛才的範例用戶端應用程式,加入另外一個TMemo元件,以及一個TEdit元件讓使用者可以輸入不同的用戶端識別ID來註冊:

接著在『啟動回叫功能1』和『啟動回叫功能2』按鈕中都使用下面的程式碼實作,現在用戶端識別ID就由使用者輸入而不是使用目前的日期:

procedure TfmMainForm.btnStartClick(Sender: TObject);

begin

SetupTask;

EnableDisableButtons(False, True, True);

DemoChannelManager.RegisterCallback(edtCallbackId.Text, TDemoCallback.Create);

aIdList.Add(edtCallbackId.Text);

end;

編譯並且執行新的用戶端應用程式,在下面的圖形中我們可以看到筆者在用戶端應用程式中註冊了兩個用戶端識別ID,分別是『用戶端識別ID1』和『用戶端識別ID2』,而範例DataSnap伺服器也可以列出這兩個不同的用戶端識別ID,在用戶端註冊了兩個不同的識別ID之後,範例DataSnap伺服器可以藉由同時回叫這兩個不同的用戶端回叫物件。例如下圖就顯示了當我們在範例DataSnap伺服器的TMemo中輸入了訊息之後,這些訊息會立刻顯示在用戶端應用程式中兩個不同的TMemo元件之中:

如何? DataSnap XE的回叫機制是不是更強大了?不過故事還沒結束,接下來我們將繼續討論如何讓不同的用戶端都能夠藉由回叫機制來溝通,以及如何開發以瀏覽器為基礎的回叫用戶端,這些是更精彩的主題,我們下次再見了, Have Fun!

 

2 則迴響

Delphi XE程式設計系列 2-開發DataSnap/REST伺服器

在上次的文章中討論了如何把傳統的Delphi 主從架構應用程式逐漸轉換為DataSnap JSON伺服器,在本篇文章中讓我們正式討論如何使用Delphi XE開發DataSnap/REST伺服器,由於這其中牽涉到非常多的技術,因此我們將花數篇的篇幅來討論。 現在就讓我們從DataSnap/REST伺服器開始。

開發DataSnap伺服器

Delphi XE版的DataSnap允許開發人員同時在DataSnap伺服器中實作RESTful架構的伺服器,如此一來DataSnap伺服器不但可以在網路內部做為多層的服務伺服器,也可以讓網路外部的用戶端使用REST的方式來存取服務。 要在Delphi XE中建立DataSnap/REST伺服器,請點選Files|New功能表,在DataSnap Server選項中選擇DataSnap Server圖像,如下圖所示:

Delphi XE提供三種不同的伺服器型態,分別是以VCL應用程式實作的伺服器,實作為主控程式的伺服器以及實作成Windows服務應用程式的伺服器,開發人員可根據自己的需求選擇建立適當的伺服器型態,在本篇文章中讓我們建立VCL應用程式型態的伺服器:

點選Next按鈕之後DataSnap精靈會如下圖詢問需要支援的通訊協定,是否使用安全驗證功能以及是否要預先建立範例服務方法,讓我們點選下方的Select All以選擇建立所有的功能,如下圖所示:

點選Next按鈕,DataSnap精靈會如下圖詢問TCP/IP和HTTP使用的通信埠,內定上TCP/IP使用211而HTTP則使用8080,開發人員可根據自己的需求設定這兩個通信埠,或是點選Find Open Port按鈕讓DataSnap精靈幫忙搜尋可使用的通信埠:

接著DataSnap精靈會詢問開發人員實作服務方法的類別,開發人員可以選擇實作於TComponent類別,TDataModule類別或是TDSServerModule類別,在本文章中我們選擇實作於TDSServerModule:

點選Finish按鈕之後,Delphi XE便會建立相對應的專案,我們開啟ServerContainerUnit的話就可以看到其中包含了如下元件,其中的TDSServer,TDSTCPServerTransport以及TDSServerClass類別元件在Delphi 2010中就存在了,新的TDSHTTPService類別元件則提供了HTTP/HTTPS通訊協定的支援,而新的TDSAuthenticationManager類別元件則提供安全驗證功能,在稍後的文章中我們會說明如何使用它。

現在DataSnap精靈會在專案的ServerMethodsUnit程式單元中產生兩個範例方法,EchoString和ReverseString。現在讓我們在這個程式單元中加入一個新的服務方法『取得部落格文章名稱』,如下所示:

public

{ Public declarations }

function EchoString(Value: string): string;

function ReverseString(Value: string): string;

function 取得部落格文章名稱 : TJSONArray;

接著實作『取得部落格文章名稱』方法,如下所示:

function TServerMethods2.取得部落格文章名稱: TJSONArray;

begin

Result := TJSONArray.Create;

Result.AddElement(TJSONString.Create(‘Delphi XE程式設計系列 1-主從架構, 多層到JSON和REST’));

Result.AddElement(TJSONString.Create(‘從原生API到REST API – 使用C++Builder XE開發REST應用程式’));

Result.AddElement(TJSONString.Create(‘Delphi XE程式設計系列 2-DataSnap/REST伺服器’));

end;

『取得部落格文章名稱』方法建立TJSONArray物件,並且把三篇文章名稱以TJSONString物件儲存在元素中,最後回傳TJSONArray物件給用戶端。

最後開啟ServerMethodsUnit程式單元的設計介面,在其中放入dbExpress元件以存取儲存在MS SQL Server資料庫中的範例資料表FishFacts,稍後我們將說明這個DataSnap/REST伺服器如何同時以傳統DataSnap的架構讓用戶端使用dbExpress元件存取資料,以及如何以REST的架構讓用戶端存取它提供的服務。

現在編譯並且執行這個DataSnap/REST伺服器。

由於現在這個伺服器同時可提供DataSnap和REST伺服器的功能,因此現在我們可以試著使用瀏覽器來使用存取這個伺服器的服務。讓我們使用下面的URI來呼叫『取得部落格文章名稱』方法:

http://localhost:8085/datasnap/rest/TServerMethods2/取得部落格文章名稱

我們可以在下圖中看到,我們果然可以在瀏覽器中使用上面的URI成功的呼叫伺服器的服務:

而且我們從上圖中可以清楚的看到回傳的結果是使用JSON格式封裝的JSON陣列,每一個陣列元素是Unicode編碼的JSON字串。

連結使用DataSnap伺服器

現在讓我們建立一個用戶端VCL應用程式專案,放入TSQLConnection元件,然後設定它的特性值如下(此時DataSnap/REST伺服器必須是在執行狀態):

特性 特性值
Driver Datasnap
Connected True

點選滑鼠右鍵,選擇建立『Generate DataSnap Client Classes』功能表,如下所示,再把產生的程式單元儲存為ServerProxy程式單元。

然後在主表單中放入如下的dbExpress和VCL元件:

設定TDSProviderConnection元件的特性值如下:

特性 特性值
SQLConnection SQLConnection1
ServerClassName TServerMethods2

再設定TClientDataSet的特性值如下:

特性 特性值
RemoteServer DSProviderConnection1
Provider dspFishFacts

當我們在設定TClientDataSet的Provider特性值時,用戶端應用程式就會連結到DataSnap/REST伺服器並且顯示ServerMethodsUnit程式單元中輸出的TDataSetProvider元件。

讓我們在『更新』按鈕的OnClick事件處理函式中撰寫如下的程式碼:

procedure TForm10.Button3Click(Sender: TObject);

begin

if (cdsFishFacts.ChangeCount > 0) then

cdsFishFacts.ApplyUpdates(0);

end;

編譯並且執行用戶端應用程式,我們就可以看到類似如下的畫面:

DataSnap/REST伺服器就如同以前的DataSnap/Midas伺服器一樣可以提供二層和多層的開發架構,用戶端應用程式也可以使用dbExpress元件來異動DataSnap/REST伺服器中的資料。

現在我們已經展示了這個DataSnap/REST伺服器可以同時使用二層/多層和REST的架構來使用它。

現在再讓我們看看如何在用戶端使用程式碼來存取伺服器的服務。在前面我們已經藉由TSQLConnection元件自動產生了ServerProxy程式單元,如果我們開啟ServerProxy,便會看到下面的類別宣告:

TServerMethods2Client = class(TDSAdminClient)

private

FEchoStringCommand: TDBXCommand;

FReverseStringCommand: TDBXCommand;

F取得部落格文章名稱Command: TDBXCommand;

public

constructor Create(ADBXConnection: TDBXConnection); overload;

constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;

destructor Destroy; override;

function EchoString(Value: string): string;

function ReverseString(Value: string): string;

function 取得部落格文章名稱: TJSONArray;

end;

如果我們觀察ServerProxy程式單元中的『取得部落格文章名稱』方法,就可以看到它也使用dbExpress技術來存取伺服器的服務:

function TServerMethods2Client.取得部落格文章名稱: TJSONArray;

begin

if F取得部落格文章名稱Command = nil then

begin

F取得部落格文章名稱Command := FDBXConnection.CreateCommand;

F取得部落格文章名稱Command.CommandType := TDBXCommandTypes.DSServerMethod;

F取得部落格文章名稱Command.Text := ‘TServerMethods2.取得部落格文章名稱’;

F取得部落格文章名稱Command.Prepare;

end;

F取得部落格文章名稱Command.ExecuteUpdate;

Result := TJSONArray(F取得部落格文章名稱Command.Parameters[0].Value.GetJSONValue(FInstanceOwner));

end;

因此在用戶端,我們可以使用下面的程式碼藉由ServerProxy程式單元中的『取得部落格文章名稱』方法來取得部落格文章資訊:

procedure TForm10.Button1Click(Sender: TObject);

var

aServer: TServerMethods2Client;

ja : TJSONArray;

iIndex: Integer;

begin

aServer := TServerMethods2Client.Create(Self.SQLConnection1.DBXConnection);

try

ja := aServer.取得部落格文章名稱;

for iIndex := 0 to ja.Size – 1 do

ListBox1.Items.Add(ja.Get(iIndex).ToString);

finally

aServer.Free;

end;

end;

下圖是用戶端應用程式執行上面程式碼的結果:

但是除了dbExpress技術之外,我們也可以使用REST,JavaScript等技術來存取伺服器服務,因為這個伺服器就是一個REST伺服器。因此讓我們更深入的討論一下如何在用戶端自動產生程式碼來支援REST和JavaScript等技術。

用戶端程式碼產生器

DataSnap XE版目前可自動產生四種用戶端程式碼讓不同的用戶端能夠連結和使用DataSnap/REST伺服器,這四種是:

DataSnap XE支援的四種用戶端程式碼 說明
Delphi DBX 使用dbExpress技術呼叫DataSnap/REST伺服器的用戶端Delphi程式碼
C++Builder DBX 使用dbExpress技術呼叫DataSnap/REST伺服器的用戶端C/C++程式碼
Java Script REST 使用REST/JSON技術呼叫DataSnap/REST伺服器的用戶端JavaScript程式碼
Delphi REST 使用REST/JSON技術呼叫DataSnap/REST伺服器的用戶端Delphi程式碼

我們可以輕易的使用下面的程式碼來取得目前能夠產生的用戶端程式碼:

procedure TForm10.ListRegisteredWriter;

var

sa : TDBXStringArray;

iIndex : Integer;

begin

sa := DSProxyWriter.TDSProxyWriterFactory.RegisteredWritersList;

for iIndex := 0 to Length(sa) – 1 do

ComboBox1.Items.Add(sa[iIndex]);

ComboBox1.ItemIndex := 0;

end;

DSProxyWriter程式單元中TDSProxyWriterFactory類別的類別方法RegisteredWritersList可以回傳目前註冊的用戶端程式碼種類,目前上表列出的四種用戶端程式碼產生器分別位於DSProxyDelphi,DSProxyCpp, DSProxyJavaScript和DSProxyDelphiRest程式單元中。

當我們要產生上表四種用戶端程式碼以呼叫特定的DataSnap/REST伺服器時,我們需要使用IDSProxyMetaDataLoader介面以及TDSProxyGenerator類別。

IDSProxyMetaDataLoader介面是由TDSProxyMetaDataLoader類別實作的,我們可以使用TDBXConnection物件建立TDSProxyMetaDataLoader物件,取得它的IDSProxyMetaDataLoader介面,再建立TDSProxyGenerator物件,設定要產生的特定用戶端程式碼目標,最後呼叫TDSProxyGenerator物件的Write方法,如此一來DataSnap框架就會自動產生連結特定DataSnap/REST伺服器的用戶端程式碼。

例如,現在讓我們來看看如何能夠要求DataSnap框架自動產生Delphi REST或是JavaScript的用戶端程式碼。

下面的程式碼首先呼叫GetMetaDataLoader方法取得IDSProxyMetaDataLoader介面,再呼叫GenerateFile藉由IDSProxyMetaDataLoader介面產生使用者特定的用戶端程式碼:

procedure TForm10.Button2Click(Sender: TObject);

var

LMetaDataLoader: IDSProxyMetaDataLoader;

begin

LMetaDataLoader := GetMetaDataLoader;

GenerateFile(LMetaDataLoader);

ShowGeneratedFiles;

end;

GetMetaDataLoader方法藉由程式中的TSQLConnection的TDBXConnection物件建立TDSProxyMetaDataLoader物件,再回傳TDSProxyMetaDataLoader物件實作的IDSProxyMetaDataLoader介面:

function TForm10.GetMetaDataLoader : IDSProxyMetaDataLoader;

begin

Result := TDSProxyMetaDataLoader.Create(

function: TDBXConnection

begin

OpenConnection;

Result := SQLConnection1.DBXConnection;

end,

procedure(AConnection: TDBXConnection)

begin

SQLConnection1.Close;

end

);

end;

而GenerateFile方法先建立TDSProxyGenerator物件,設定它的Writer特性值為稍後使用者在程式中設定的特定的用戶端程式碼的名稱,例如是『Delphi DBX』產生使用dbExpress技術的用戶端程式碼,或是『Java Script REST』產生使用REST/JSON的JavaScript程式碼,最後呼叫Write方法實際的產生用戶端程式碼:

procedure TForm10.GenerateFile(AMetaDataLoader: IDSProxyMetaDataLoader);

var

LProxyGenerator: TDSProxyGenerator;

begin

LProxyGenerator := TDSProxyGenerator.Create(nil);

try

LProxyGenerator.Writer := ComboBox1.Text;

LProxyGenerator.TargetUnitName := ‘GeneratedServerProxy’;

LProxyGenerator.ExcludeMethods := “;

LProxyGenerator.ExcludeClasses := “;

LProxyGenerator.TargetDirectory := ‘.’;

LProxyGenerator.OnCreatingFiles := ACreatingFiles;

LProxyGenerator.OnCreatedFiles := ACreatedFiles;

LProxyGenerator.Write(AMetaDataLoader);

finally

LProxyGenerator.Free;

end;

end;

現在如果我們執行用戶端應用程式,可以看到如下的畫面,在下面中我選擇產生Delphi REST的用戶端程式碼:

那麼這個範例用戶端應用程式便會自動產生使用REST的用戶端Delphi程式碼,例如它產生的呼叫範例DataSnap/REST伺服器的『取得部落格文章名稱』方法的程式碼如下:

function TServerMethods2Client.取得部落格文章名稱(const ARequestFilter: string): TJSONArray;

begin

if F取得部落格文章名稱Command = nil then

begin

F取得部落格文章名稱Command := FConnection.CreateCommand;

F取得部落格文章名稱Command.RequestType := ‘GET’;

F取得部落格文章名稱Command.Text := ‘TServerMethods2.取得部落格文章名稱’;

F取得部落格文章名稱Command.Prepare(TServerMethods2_取得部落格文章名稱);

end;

F取得部落格文章名稱Command.Execute(ARequestFilter);

Result := TJSONArray(F取得部落格文章名稱Command.Parameters[0].Value.GetJSONValue(FInstanceOwner));

end;

看到現在它是使用HTTP的Get命令,藉由REST呼叫慣例來呼叫DataSnap/REST伺服器的『取得部落格文章名稱』方法了。

如果我是選擇產生Java Script REST,

那麼下面就是DataSnap框架自動產生的用戶端JavaScript程式碼:

/*

* @return result – Type on server: TJSONArray

*/

this.取得部落格文章名稱 = function() {

var returnObject = this.executor.executeMethod(‘取得部落格文章名稱’, “GET", [], arguments[0], true, arguments[1], arguments[2]);

if (arguments[0] == null) {

if (returnObject != null && returnObject.result != null && isArray(returnObject.result)) {

var resultArray = returnObject.result;

var resultObject = new Object();

resultObject.result = resultArray[0];

if (returnObject.cacheId != null && returnObject.cmdIndex != null) {

resultObject._cacheId = returnObject.cacheId;

resultObject._cmdIndex = returnObject.cmdIndex;

}

return resultObject;

}

return returnObject;

}

};

this.取得部落格文章名稱_URL = function() {

return this.executor.getMethodURL(“取得部落格文章名稱", “GET", [], arguments[0])[0];

};

}

var JSProxyClassList = {

“TServerMethods2″: [“DSServerModuleCreate","DSServerModuleDestroy","EchoString","ReverseString","取得部落格文章名稱"]

};

最後我試著同時使用Delphi用戶端應用程式以及瀏覽器兩個不同的用戶端來呼叫和使用範例DataSnap/REST伺服器,看起來一切都非常的美好:

當然,我也可以使用純粹的Web用戶端應用程式來呼叫和使用範例DataSnap/REST伺服器,例如下圖就是我使用VCL For Web XI來使用範例DataSnap/REST伺服器的結果,所有的服務仍然工作良好:

DataSnap XE版藉由擴充多層架構到REST和JSON的技術領域,讓DataSnap XE瞬間突破了平台的限制,允許Delphi,C/C++Builder,JavaScript,PHP,Ruby和移動設備等各種用戶端能夠使用它的服務,再次賦予了DataSnap框架無限的發展潛能。

好了,時間已晚,我們也下次再見了。

4 則迴響

Delphi XE程式設計系列 1-主從架構, 多層到JSON和REST

從桌面開發,主從架構,一直到多層架構,雖然都是廣泛被接受的觀念和技術,但在資訊技術的實作上卻從不是開放,相容的世界。Delphi從桌面開發到主從架構都是使用自己的資料傳遞格式以及通訊傳遞架構,到了多層架構雖然使用了Windows平台上的通訊協定,例如COM/DCOM/COM+,但是在傳遞的資料格式方面仍然是使用自己的架構,COM/DCOM/COM+也是MS專屬的通訊協定,和其他平台上使用的通訊協定也不一樣。當然,不光是Delphi/BCB,大部份的開發工具也是採用類似的方式,那就是都支援桌面開發,主從架構或是多層架構等通用觀念的架構,但使用來傳遞資料和溝通通訊協定都是封閉的架構。
直到JSON和REST的出現以及Delphi/BCB確定走向原生,跨平台的道路之後,Delphi/BCB從2010版便開始走向以JSON封裝資料,以REST做為通訊架構的方向。因此Delphi/BCB除了仍然支援原有的資料封裝格式以及通訊協定之外,也允許開發人員選擇使用JSON和REST,使用JSON和REST的好處是除了可以讓JSON和REST擁抱最新的資訊技術之外,也可以讓Delphi/BCB在不同的平台中使用相同的技術來開發主從架構,分散式多層以及Web應用,也可以更容易的和其他的程式語言,框架和技術整合在一起。
現在讓我們重溫舊夢一下,看看如何把一個簡單的主從架構應用程式轉換為使用JSON的架構。

    主從架構

下圖是一個簡單的主從架構的主表單,

它藉由下圖的dbExpress元件從資料庫的FishFacts資料表中取得資料,並且使用資料感知元件顯示在應用程式的主表單中。

雖然從BDE到dbExpress都使用專屬的格式封裝資料,但BDE和dbExpress也可以把資料封裝成較開放的XML格式,因此要把上圖中TClientDataSet中的資料轉換為XML的格式,我們只需要存取它的XMLData特性值即可:

dssmFishFact.cdsFishFact.XMLData;

XMLData特性值會回傳以下面格式封裝的XML資料:

<?xml version="1.0″ encoding="UTF-8″ standalone="yes"?>  <DATAPACKET Version="2.0″><METADATA><FIELDS><FIELD attrname="Category" fieldtype="string" WIDTH="15″/><FIELD fieldname="Species Name" attrname="Species_Name" fieldtype="string" WIDTH="40″/><FIELD fieldname="Length (cm)" attrname="Length__cm_" fieldtype="r8″/><FIELD attrname="Length_In" fieldtype="r8″/><FIELD attrname="Common_Name" fieldtype="string" WIDTH="30″/><FIELD attrname="Notes" fieldtype="string" WIDTH="50″/><FIELD attrname="Graphic" fieldtype="bin.hex" SUBTYPE="Binary" WIDTH="1″/><FIELD fieldname="Species No" attrname="Species_No" fieldtype="r8″/></FIELDS><PARAMS LCID="0″/></METADATA><ROWDATA><ROW Category="Triggerfish" Species_Name="Ballistoides conspicillum" Length__cm_="50″ Length_In="19.68503937007874″ Common_Name="Clown Triggerfish" Notes="Also known as the big spotted triggerfish.  Inhabi"…

然而BDE/dbExpress雖然能夠把資料封裝成XML格式,但使用XML封裝資料時仍然會因為不同的資料存取使用不同的XML元素來封裝資料,因此在交換資料時仍然會造成許多的困擾,而且使用XML格式封裝資料的成本比起JSON來要昂貴許多(XML使用較多元素,較為複雜的規則封裝資料所致)。

因此Delphi/BCB要支援JSON/REST技術,其中一個工作就是必須能夠把資料封裝成JSON的格式,因此從Delphi/BCB 2010版開始便在VCL和RTL中加入了許多和JSON相關的類別以執行這項工作。到了XE版Delphi/BCB基本上不但能夠把資料封裝成JSON的格式,甚至提供了REST的API允許Delphi,BCB和任何支援JSON和REST的用戶端和使用Delphi/BCB開發的DataSnap伺服器整合在一起,下圖敘述了Delphi/BCB XE版支援的架構:

OK,現在先讓我們看看Delphi/BCB XE如何能夠把資料封裝成JSON的格式,以及提供解析,處理JSON封包的相關類別。稍後我們再討論Delphi/BCB XE如何支援REST API的使用和呼叫。

其實要把dbExpress中的資料封裝成JSON非常的簡單,就以直覺來說,我們只需要一個單向,唯讀的資料集,這個單向,唯讀的資料集可以直接從dbExpress的資料集元件建立,接著再一一的從這個單向,唯讀的資料集中讀取資料,根據JSON規則封裝即可。

因此在VCL中提供了TDBXDataSetReader這個單向,唯讀的資料集,它可以從dbExpress資料集元件建立,接著在TDBXJSONTools類別中提供了TableToJSON類別方法,它接受一個TDBXReader物件為第一個參數,第二個參數為要從第一個參數中封裝的記錄筆數,最後一個參數則代表在TableToJSON執行完畢之後是否需要釋放第一個參數物件:

class function TableToJSON(const Value: TDBXReader; const RowCount: Integer; const IsLocalConnection: Boolean): TJSONObject; static;

因此要封裝範例FishFacts資料表中的2筆資料為JSON格式,我們就可以使用下面的程式碼來完成這個工作。

在下面的程式碼中我們首先藉由資料模組中的TClientDataSet元件建立TDBXDataSetReader元件,接著呼叫TDBXJSONTools類別的TableToJSON類別方法把資料封裝成JSON格式:

procedure TForm1.btnToJSONClick(Sender: TObject);

var

aDBXReader : TDBXReader;

aJSonObj : TJSONObject;

begin

aDBXReader := TDBXDataSetReader.Create(dssmFishFact.cdsFishFact, False);

try

aJSonObj := TDBXJSONTools.TableToJSON(aDBXReader, 2, False);

Memo2.Lines.Text := aJSonObj.ToString;

ParseData(aJSonObj);

finally

aJSonObj.Free;

aDBXReader.Free;

end;

end;

下圖是執行

dssmFishFact.cdsFishFact.XMLData;

之後得到的XML格式的結果:

而下圖則是藉由TableToJSON轉換為JSON格式的結果:

如果我們觀察JSON格式的結果可以看到DataSnap是以JSON物件封裝資料,而每一個欄位則是以JSON陣列來封裝:

{“table":[[“Category",1,0,0,15,16,0,0,false,false,0,false,false],[“Species Name",1,1,0,40,41,0,0,false,false,0,false,false]….

由於使用JSON格式封裝資料比較簡單而且在解析上也比XML容易,我們可以使用VCLJSON相關的類別很容易的解析出其中封裝的資料。例如下面的ParseData從前面TableToJSON建立的JSON物件中解析其中封裝的FishFacts資料表的資料:

procedure TForm1.ParseData(aJSONObj: TJSONObject);

var

iPair : Integer;

aPair : TJSONPair;

begin

for iPair := 0 to aJSONObj.Size – 1 do

begin

aPair := aJSONObj.Get(iPair);

if (aPair.JsonString.ToString <> ‘"Graphic"‘) then

Memo3.Lines.Add(Format(‘%s : %s’, [aPair.JsonString.ToString, aPair.JsonValue.ToString]));

end;

end;

下圖即是ParseData執行後的結果:

由於DataSnap可以使用JSON封裝資料,因此任何支援JSON的程式語言或是框架都可以處理DataSnap封裝的資料,這也代表任何支援JSON的用戶端都可以連結到Delphi/BCB XE建立的DataSnap JSON伺服器並且呼叫它提供的服務。

OK,現在我們瞭解了如何使用DataSnap封裝資料為JSON格式,因此我們現在可以很容易的把這個傳統的主從架構應用程式轉換為DataSnap JSON伺服器,如此一來我們就提供了如何把傳統主從架構架逐漸構轉換為分散式JSON架構的可能性。

我們的第一步是把這個主從架構應用程式轉換為DataSnap JSON伺服器,要如此做我們需要讓這個主從架構把資料以JSON的格式輸出,以便用戶端能夠存取,使用。


轉換主從架構應用程式為DataSnap JSON伺服器

為了輸出主從架構應用程式的資料,讓我們首先在這個主從架構專案中建立一個Server Module,如下所示。Server Module能夠自動把包含它的應用程式的資料或是服務輸出給用戶端使用。

由於我們現在需要把主從架構應用程式中的資料輸出以便讓用戶端應用程式能夠存取,因此我們需要把原本主從架構中資料模組中的dbExpress相關元件移動這個建立的Server Module中,接著在原本主從架構的資料模組中加入TDSServerTDSTCPServerTransportTDSServerClass元件,如下所示:

接著在TDSServerClass元件的GetClass事件處理函式中設定它的PersistentClass參數為Server Module中的TClientDataSet元件類別:

procedure TdmFishFact.dsscFishFactGetClass(DSServerClass: TDSServerClass;

var PersistentClass: TPersistentClass);

begin

PersistentClass := usmFishFact.TdssmFishFact;

end;

完成了這個簡單的工作之後,現在如果我們編譯並且再次執行這個主從架構應用程式,那麼現在它不但仍然可以做為傳統主從架構應用程式來使用,它現在也已經成為了一個DataSnap JSON伺服器,現在我們就可以建立一個DataSnap用戶端來連結它並且取得FishFacts的資料。

建立Delphi DataSnap用戶端

建立一個VCL Form應用程式專案,在主表單中加入如下的元件:

要連結前面的範例DataSnap伺服器,我們只需要加入TSQLConnection元件,並且設定它的特性如下:

特性

特性值

Driver

Datasnap

加入一個TDSProviderConnection元件,設定它的特性值如下:

特性

特性值

SQLConnection

SQLConnection1

ServerClassName

TdssmFishFact

Name

DSPCFishFact

加入一個TClientDataSet元件,設定它的特性值如下:

特性

特性值

RemoteServer

DSPCFishFact

ProviderName

dspFishFact

在上面的設定中關鍵的兩個設定是TDSProviderConnection元件的ServerClassName特性值必須設定為DataSnap伺服器中Server Module的類別名稱,以及TClientDataSet元件的ProviderName必須設定為Server Module中的TDataSetProvider元件。而在這個範例DataSnap伺服器中的Server Module的類別名稱就是TdssmFishFact,而Server Module中的TDataSetProvider元件名稱則是dspFishFact

下圖就是設定TDSProviderConnection元件畫面:

而下面則是設定TClientDataSet元件的物件檢視器:

設定好了之後只要再連結相關的資料感知元件就可以完成用戶端應用程式了。

現在如果我們執行範例主從架構應用程式兼DataSnap伺服器,再執行DataSnap用戶端應用程式,那麼我們可以看到類似下面的畫面:

上圖中範例主從架構應用程式兼DataSnap伺服器執行時既是傳統的主從架構,也是DataSnap伺服器,因此右下方的DataSnap用戶端應用程式執行之後才能夠從這個主從架構應用程式兼DataSnap伺服器取得FishFacts資料。

如何? 瞭解了dbExpress/DataSnap如何使用JSON封裝資料的原理之後我們就可以容易的把它轉換為DataSnap伺服器。讀者可以使用類似的方式在保留主從架構架構的同時又逐漸的把主從架構轉換為DataSnap的分散式JSON架構。

我們下次再談談Delphi如何支援REST API,如此一來我們就可以讓其他的JSON用戶端連結並且使用DelphiDataSnap JSON伺服器提供的服務,再見了。


4 則迴響