維's profileIT : 是工作還是嗜好?PhotosBlogListsMore Tools Help

Blog


    September 28

    JSON 程式設計 – 使用過濾器

    在以前Tiburon遊記的文章解釋過JSON是使用字串型態來傳遞資料的,因此所有其他型態的資料都必須轉換為字串的型態,雖然如此一來在處理上比較簡單,但這也造成了其他的問題,例如一些敏感性的資料如果使用字串型態來傳遞的話就會有問題。DataSnap 2010為了解決這種問題因此加入了過濾器的機制,讓開發人員在傳遞特殊的資料時可以藉由過濾器來進行額外的處理,例如在傳遞資料出去時先加密,並且在接受到資料之後再進行解密。本篇文章的目的即在於討論如何使用DataSnap 2010的過濾器。

    使用DataSnap 2010的過濾器非常的簡單,Delphi 2010也內建了一個壓縮過濾器,可以有效的壓縮使用TCP/IP通訊協定的資料傳遞。讓我們先說明如何使用這個內建的過濾器,稍後我們再深入的說明如何開發客製化過濾器。

    讓我們仍然使用上篇文章的範例,要使用過濾器,請開啟ServerContainerUnit程式單元,點選表單中的TDSTCPServerTransport元件,並且雙擊它的Filters特性,此時Filters特性值編輯器會啟動,請於其中加入一個新的過濾器,點選此新的過濾器,然後在物件檢視器中選擇它的FilterId為ZlibCompression,如下所示:

    clip_image002

    ZlibCompression過濾器就是DataSnap 2010內建的壓縮過濾器,在加入了ZlibCompression過濾器之後,編譯並且執行範例DataSnap伺服器,現在DataSnap伺服器就提供了壓縮JSON資料的能力。

    現在再讓我們開啟用戶端應用程式,因為我們要在用戶端應用程式中加入解壓縮資料的能力,這個非常的簡單,我們只要在用戶端應用程式的主表單中加入使用DBXCompressionFilter程式單元即可,例如下面就是用戶端應用程式加入DBXCompressionFilter程式單元的程式碼:

    implementation

    uses DBXJSONReflect, DBXJSON, uServerProxy, uEmployee, DBXCompressionFilter;

    現在編譯並且執行用戶端應用程式,並且讓我們使用TCP Viewer來觀察使用壓縮過濾器之前的情形以及使用壓縮過濾器之後的效果。

    下圖是TCP Viewer顯示範例DataSnap應用系統使用壓縮過濾器之前的情形,從下圖中我們可以看到在DataSnap伺服器和用戶端應用程式之間傳遞的資料當然是使用字串的型態,所有傳遞的資料都一清二楚,同時請讀者注意下圖右邊顯示了從伺服器傳遞到用戶端的資料量(938位元組)以及從用戶端傳遞到伺服端的資料量(706位元組)。

    clip_image004

    而下圖則是使用壓縮過濾器之後的效果:

    clip_image006

    從上圖中可以看到傳遞的資料經過壓縮,因此不易看出原始的資料,而且請讀者注意下圖右邊顯示了從伺服器傳遞到用戶端的資料量(653位元組)以及從用戶端傳遞到伺服端的資料量(602位元組),可見到壓縮過濾器有效的減少了伺服器和用戶端之間的資料傳遞量,這不但可以增加分散式應用程式的執行速度,也可以增加支援的用戶端的數量。

    如何? 使用過濾器是不是又簡單,又有明顯的效果? 不過DataSnap 2010只提供了一個內建的過濾器實在太少,好在DataSnap 2010過濾器架構在設計時就考慮到了允許讓開發人員能夠自行開發過濾器並且內嵌到DataSnap之中,接下來筆者將討論如何開發客製化過濾器並且使用在DataSnap 2010的分散式應用系統中。

    開發客製化過濾器

    要開發客製化過濾器,開發人員必須從TTransportFilter類別衍生子代類別並且實作TTransportFilter類別中相關的虛擬方法,下面的表單說明了開發人員需要實作的虛擬方法:

    函式名稱

    回傳型態

    說明

    GetParameters

    TDBXStringArray

    回傳所有的參數

    GetUserParameters

    TDBXStringArray

    回傳使用者可改變的參數

    ProcessInput

    TBytes

    使用客製化程式碼正向處理傳遞的資料流

    ProcessOutput

    TBytes

    使用客製化程式碼反向處理傳遞的資料流

    Id

    UnicodeString

    過濾器的ID

    GetParameterValue

    UnicodeString

    取得特定名稱的參數值

    SetParameterValue

    Boolean

    設定特定名稱的參數值

    瞭解了需要實作那些虛擬方法之後,我們就可以開始動手開發一個客製化過濾器了。在本文中筆者將撰寫一個非常簡單的加密/解密過濾器,其實這個加密/解密過濾器只是在傳遞資料時和一個字串進行xor的動作,到了另一端再次xor相同的字串而已。

    首先讓我們宣告範例TTransportEncryptFilter類別從TTransportFilter類別繼承下來並且複載相關必要的虛擬方法:

    TTransportEncryptFilter = class(TTransportFilter)

    private

    FEncrypt: TSimpleEncryptor;

    FParameters: TDictionary<String, String>;

    protected

    function GetParameters: TDBXStringArray; override;

    function GetUserParameters: TDBXStringArray; override;

    public

    function GetParameterValue(const ParamName: UnicodeString): UnicodeString;

    override;

    function SetParameterValue(const ParamName: UnicodeString;

    const ParamValue: UnicodeString): Boolean; override;

    constructor Create; override;

    destructor Destroy; override;

    function ProcessInput(const Data: TBytes): TBytes; override;

    function ProcessOutput(const Data: TBytes): TBytes; override;

    function Id: UnicodeString; override;

    end;

    TTransportEncryptFilter類別將使用TSimpleEncryptor進行字串xor的運算,在TSimpleEncryptor類別中實作了兩個方法,Encrypt和Decrypt,其實這兩個方法的實作程式碼是一樣的,只是為了說明方便分別實作成Encrypt和Decrypt以便讓讀者瞭解。Encrypt和Decrypt接受TBytes型態的參數,這個參數在Encrypt方法中是代表傳遞出去的資料,Encrypt方法使用程式碼加密之後再把加密過的資料以TBytes型態回傳。

    而Decrypt的參數則是代表接受來的資料,Decrypt方法必須使用程式碼加以解密以還原資料。

    TSimpleEncryptor = class

    protected

    public

    function Encrypt(const Data: TBytes): TBytes;

    function Decrypt(const Data: TBytes): TBytes;

    constructor Create;

    end;

    下面是這兩個方法的實作程式碼,讀者可以看到這兩個方法的實作程式碼是 樣的,它們都接受的參數以'DexterHighlanderTiburonWeaver'這個鍵值字串進行xor的運算:

    const

    EncryptKey = 'DexterHighlanderTiburonWeaver';

    constructor TSimpleEncryptor.Create;

    begin

    inherited Create;

    end;

    function TSimpleEncryptor.Decrypt(const Data: TBytes): TBytes;

    var

    i: Integer;

    idx: Integer;

    begin

    Result := Data;

    idx := 0;

    for i := 0 to Length(Data) - 1 do

    begin

    Result[i] := Byte(Chr(Ord(Data[i]) xor Ord(EncryptKey[idx])));

    Inc(idx);

    if (idx > Length(EncryptKey)) then

    idx := 0;

    end;

    end;

    function TSimpleEncryptor.Encrypt(const Data: TBytes): TBytes;

    var

    i: Integer;

    idx: Integer;

    begin

    Result := Data;

    idx := 0;

    for i := 0 to Length(Data) - 1 do

    begin

    Result[i] := Byte(Chr(Ord(Data[i]) xor Ord(EncryptKey[idx])));

    Inc(idx);

    if (idx > Length(EncryptKey)) then

    idx := 0;

    end;

    end;

    下面則是TTransportEncryptFilter類別的實作程式碼,讀者可以看到在在建構函式中建立了TSimpleEncryptor物件,並且分別在ProcessInput虛擬方法中呼叫TSimpleEncryptor物件的Encrypt方法加密傳遞的資料並且在ProcessOutput虛擬方法中呼叫TSimpleEncryptor物件的Decrypt方法以解密資料:

    function TTransportEncryptFilter.GetUserParameters: TDBXStringArray;

    begin

    SetLength(Result, 1);

    Result[0] := EncryptKey;

    end;

    function TTransportEncryptFilter.GetParameters: TDBXStringArray;

    begin

    SetLength(Result, 1);

    Result[0] := EncryptKey;

    end;

    function TTransportEncryptFilter.GetParameterValue

    (const ParamName: UnicodeString): UnicodeString;

    begin

    FParameters.TryGetValue(ParamName, Result);

    if ( ParamName = EncryptKey ) and ( Result = '' ) then

    Result := '0';

    end;

    function TTransportEncryptFilter.SetParameterValue

    (const ParamName, ParamValue: UnicodeString): Boolean;

    begin

    FParameters.AddOrSetValue(ParamName, ParamValue);

    Result := True;

    end;

    constructor TTransportEncryptFilter.Create;

    begin

    inherited Create;

    FParameters := TDictionary<String, String>.Create;

    FEncrypt := TSimpleEncryptor.Create;

    end;

    destructor TTransportEncryptFilter.Destroy;

    begin

    FreeAndNil(FParameters);

    FreeAndNil(FEncrypt);

    inherited Destroy;

    end;

    function TTransportEncryptFilter.ProcessInput(const Data: TBytes): TBytes;

    begin

    OutputDebugString(PWideChar('Encrypted - ' + Stringof(Data)));

    Result := FEncrypt.Encrypt(Data);

    end;

    function TTransportEncryptFilter.ProcessOutput(const Data: TBytes): TBytes;

    begin

    OutputDebugString(PWideChar('Decrypted - ' + Stringof(Data)));

    Result := FEncrypt.Decrypt(Data);

    end;

    function TTransportEncryptFilter.Id: UnicodeString;

    begin

    Result := EncryptFilterName;

    end;

    最後我們需要在initialization部份註冊這個客製化過濾器並且在finalization部份解除註冊客製化過濾器:

    initialization

    TTransportFilterFactory.RegisterFilter(EncryptFilterName,

    TTransportEncryptFilter);

    finalization

    TTransportFilterFactory.UnregisterFilter(EncryptFilterName);

    使用客製化過濾器

    OK,回到範例伺服器,開啟ServerContainer程式單元並且在它的OnCreate事件處理函式中使用TDSTCPServerTransport的AddFilter方法加入我們的客製化過濾器:

    procedure TServerContainer1.DataModuleCreate(Sender: TObject);

    var

    i : Integer;

    begin

    i := DSTCPServerTransport1.Filters.AddFilter(uEncryptFilter.EncryptFilterName);

    for i := 0 to DSTCPServerTransport1.Filters.Count - 1 do

    Form1.lbFilters.Items.Add(DSTCPServerTransport1.Filters.GetFilter(i).Id);

    end;

    當然我們也需要在ServerContainer程式單元的uses句子中加入包含客製化過濾器的程式單元uEncryptFilter:

    implementation

    uses Windows, ServerMethodsUnit1, MainForm, uEncryptFilter;

    現在執行DataSnap伺服器,我們就可以看到伺服器顯示它已經找到了我們的客製化過濾器:

    clip_image008

    接著開啟用戶端應用程式,在主表單中也加入客製化過濾器的程式單元uEncryptFilter,編譯並且執行用戶端應用程式,再使用TCP Viewer觀察傳遞的資料,我們果然看到資料現在都經過加密了:

    clip_image010

    但是我們可以看到傳遞的資料量增加了。

    但是用戶端仍然可以正確的接受到資料:

    clip_image012

    當然我們也可以同時使用兩個過濾器,享受加密又壓縮的好處,下圖是伺服器同時支援了兩個過濾器:

    clip_image014

    如果我們再次使用TCP Viewer,就可以看到下圖,享受加密又壓縮的好處,因為資料加密了而且資料傳遞量又減少了。

    clip_image016

    現在您應該瞭解了如何使用DataSnap 2010的過濾器功能以及如何開發客製化過濾器,我們下次再討論DataSnap 2010的非同步機制。

    Have Fun!

    September 23

    JSON 程式設計 – Marshaling/Unmarshaling

    上篇文章中說明了如何使用Delphi 2010中通用的JSON相關類別來進行程式設計並且以一個TEmployee類別做為範例來說明如何把TEmployee物件轉換為JSON格式的資料並且藉由DataSnap傳遞到用戶端。

    由於類似這種傳遞Value Object的應用在JSON程式設計的世界被非常廣泛的應用,因此DataSnap 2010正式加入了JSON Marshaling/Unmarshaling的功能,允許程式師把物件轉換為符合JSON格式的序列化物件傳遞,例如從伺服端傳遞到用戶端。這樣做的好處是只要支援JSON的技術都可以自由的解析其中的資料並且加入處理,如此一來DataSnap 2010的JSON Marshaling/Unmarshaling技術就可以達成和同質或是異質平台互動的能力,也就是說DataSnap 2010的JSON Marshaling/Unmarshaling技術是已經是一個跨平台的分散式架構了。

    使用DataSnap 2010的JSON Marshaling/Unmarshaling非常的簡單,因為對大多數的物件而言,DataSnap 2010能夠自動的把它轉換為TJSONObject型態的物件,然後再加以傳遞,因此開發人員只需要撰寫非常簡易的程式碼就可以輕易的完成。現在讓我們仍然使用上篇文章中的TEmployee類別做為說明,看看使用JSON Marshaling/Unmarshaling功能之後在分散式環境中傳遞物件是如何的容易。

    建立DataSnap伺服器

    Delphi 2010提供了新的DataSnap精靈幫助開發人員快速建立DataSnap伺服器,如此一來我們就不必每次都不斷重覆的放入TDSServer和TDSTCPServerTransport等元件,可節省我們的開發時間。此外DataSnap 2010也開始支援HTTP/HTTPS通訊協定,DataSnap精靈允許開發人員自動設定更多的元件組態資訊。

    啟動Delphi 2010,點選File|New啟動New Items對話盒並且於DataSnap Server分類中選擇DataSnap Server圖像以建立DataSnap伺服器:

    Image1

    接著在New DataSnap Server對話盒中讓我們建立一般的VCL Forms應用程式做為DataSnap伺服器的型態,選擇使用TCP/IP和HTTP/HTTPS通訊協定,並且選擇使用TDSServerModule做為類別做為輸出服務的父代類別,如下所示:

    Image2

    點選OK按鈕之後Delphi 2010便會為自動我們建立如下的ServerContainerUnit1程式單元,其中即包含了所有必要的元件。其中的TDSHTTPService和TDSHTTPServiceAuthenticationManager是新的元件,這兩個元件是使用來支援HTTP/HTTPS通訊協定的。

    Image3

    現在讓我們把Unit1程式單元儲存為名為MainForm的程式單元:

    Image4

    接著點選TDSHTTPService元件,設定它的HttpPort特性值為8080,因為通信埠80已經被Web伺服器使用,因此在這個範例中我使用8080,如果通信埠8080在讀者的機器中已經被使用,那麼您可以使用任何尚未使用的通信埠。請注意TDSHTTPService的RESTContext特性,您可以看到它的特性值是Rest,這代表Delphi 2010的DataSnap伺服器一旦使用TDSHTTPService元件就自動支援RESTful的功能,我們稍後再討論RESTful。

    Image5

    由於Delphi 2010強化了RTTI提供Reflection的機制,因此DataSnap 2010允許開發人員選擇使用TDSServerModule,TDataModule或是TPersistent類別做為輸出服務的父代類別,因為Delphi 2010都可以藉由RTTI從任何父代類別取得必要的資訊。

    現在讓我們在這個DataSnap伺服器專案中加入上篇文章中的TEmployee程式單元,並且輸出GetEmployee方法讓用戶端能夠從伺服端取得TEmployee物件。請注意GetEmployee回傳TJSONValue型態的數值,由於DataSnap 2010在參數列和回傳值都開始支援TJSONValue,因此這代表只要我們把任何物件轉換為TJSONValue物件,就可以自由傳遞到用戶端,或是從用戶端傳遞回伺服端,而這個流程就是DataSnap 2010的JSON Marshaling/Unmarshaling技術。

    TServerMethods1 = class(TDSServerModule)

    private

    { Private declarations }

    public

    { Public declarations }

    function EchoString(Value: string): string;

    function GetEmployee : TJSONValue;

    end;

    要使用JSON Marshaling/Unmarshaling,請加入使用DBXJSONReflect程式單元,如下004行所示。

    要以JSON格式傳遞物件非常的簡單,首先我們需要建立我們要傳遞的物件,一個TJSONMarshal物件以及一個TJSONConverter物件。因此下面的011行先建立TEmployee物件,012行建立TJSONMarshal物件,並且在它的建構函式中傳入一個TJSONConverter物件,最後我們只需要呼叫TJSONMarshal物件的Marshal虛擬方法並且傳入要傳遞的物件即可,如014行所示:

    01 implementation

    002

    003 {$R *.dfm}

    004 uses DBXJSONReflect, uEmployee;

    005

    006 function TServerMethods1.GetEmployee: TJSONValue;

    007 var

    008 anEmployee : TEmployee;

    009 aMarshaler : TJSONMarshal;

    010 begin

    011 anEmployee := TEmployee.Create('王大明', 'wtm@somemail.com', '123456');

    012 aMarshaler := TJSONMarshal.Create(TJSONConverter.Create);

    013 try

    014 Result := aMarshaler.Marshal(anEmployee);

    015 finally

    016 FreeAndNil(anEmployee);

    017 FreeAndNil(aMarshaler);

    018 end;

    019 end;

    如此一來TJSONMarshal和TJSONConverter類別就可以自動把傳入的物件轉換為JSON的格式並且傳遞出去。

    建立DataSnap用戶端

    現在建立一個VCL Form應用程式專案,放入TSQLConnection元件,連結到DataSnap伺服器(如果您不知道如何做,請參考筆者的Tiburon遊記系列文章),建立代表DataSnap伺服器輸出服務的用戶端程式單元uServerProxy,同樣把TEmployee程式單元加入到用戶端專案中,再參考DBXJSONReflect和DBXJSON程式單元,如下001行所示。

    在用戶端要反轉JSON物件回正確物件的流程就是Unmarshaling,我們只需要在013行建立TJSONUnMarshal物件,015行呼叫DataSnap伺服器的GetEmployee服務方法,GetEmployee回傳TJSONValue型態的物件,016行先存取回傳的TJSONValue物件的JSON格式文字值,接著018行呼叫TJSONUnMarshal的Unmarshal方法,傳入回傳的TJSONValue物件並且使用as運算元把它正確的轉換回TEmployee物件,之後我們就可以自由的存取TEmployee物件的特性值了。

    001 uses DBXJSONReflect, DBXJSON, uServerProxy, uEmployee;

    002

    003 {$R *.dfm}

    004

    005 procedure TForm2.Button1Click(Sender: TObject);

    006 var

    007 aServer : TServerMethods1Client;

    008 unMarshaler : TJSONUnMarshal;

    009 aValue : TJSONValue;

    010 anEmployee : TEmployee;

    011 begin

    012 aServer := TServerMethods1Client.Create(Self.SQLConnection1.DBXConnection);

    013 unMarshaler := TJSONUnMarshal.Create;

    014 try

    015 aValue := aServer.GetEmployee;

    016 Edit4.Text := aValue.ToString;

    017

    018 anEmployee := unMarshaler.Unmarshal(aValue) as TEmployee;

    019 Edit1.Text := anEmployee.Name;

    020 Edit2.Text := anEmployee.EMail;

    021 Edit3.Text := anEmployee.Phone;

    022

    023 finally

    024 FreeAndNil(anEmployee);

    025 FreeAndNil(unMarshaler);

    026 FreeAndNil(aServer);

    027 end;

    028 end;

    從下圖可以看到TEmployee物件的確可以正確的從DataSnap伺服器傳遞到用戶端,當然用戶端也可以修改數值之後再傳遞回DataSnap伺服器再更新回資料庫。

    Image6

    使用DataExplorer存取DataSnap伺服器的服務

    由於DataSnap 2010自動支援RESTful架構,因此既然在前面我們選擇讓DataSnap伺服器支援HTTP/HTTPS通訊協定,因此我們現在就可以使用瀏覽器來存取這個DataSnap伺服器的服務,我們只需要使用下面的樣例就可以存取到DataSnap伺服器的服務:

    http://server url/datasnap/rest/服務程式單元/服務方法

    https://server url/datasnap/rest/服務程式單元/服務方法

    因此要存取前面的DataSnap伺服器,我們只需要使用下面的URL:

    http://localhost:8080/datasnap/rest/TServerMethods1/GetEmployee

    使用8080當然是因為我們前面設定TDSHTTPService元件的HttpPort特性值使用8080。

    例如下圖就是筆者使用FireFox存取DataSnap伺服器:

    Image7

    使用瀏覽器存取DataSnap伺服器的服務

    我們也可以使用Delphi/BCB的DataExplorer來測試和存取DataSnap伺服器,例如下圖的三個圖形分別顯示了筆者使用DataExplorer來測試DataSnap伺服器是否支援TCP/IP,HTTP/HTTPS通訊協定,以及存取GetEmployee服務。請注意第3個圖形,它明確的顯示了DataSnap伺服器回傳的是物件型態(Object)。

    Image8 Image9 Image10

    建立Web用戶端

    既然範例DataSnap伺服器支援HTTP/HTTPS通訊協定,因此我們當然可以使用Web應用程式來存取它的服務,例如下圖就是筆者使用VCL For Web建立Web應用程式並且存取DataSnap伺服器的服務:

    Image11

    這是如何做到的?

    由於篇幅的限制,因此讓我們簡單的說明一下好了。當開發人員使用TJSONMarshal類別Marshaling物件時,TJSONMarshal藉由新的Rtti功能取得任何物件的欄位值,再一一的根據欄位型態呼叫TXXXXConverter類別把欄位值轉換為JSON格式,最後再把所有欄位值組合成TJSONObject的型態傳遞出去。至於Unmarshaling則是進行相反的動作。

    那麼DataSnap 2010的Marshaling/Unmarshaling能夠傳遞什麼型態的物件呢? 沒有限制嗎? 這要分2個層面回答。

    首先回答第一個問題,Marshaling/Unmarshaling能夠傳遞什麼型態的物件呢?答案是任何從TObject繼承下來的物件都可以,因為Marshaling/Unmarshaling中的實作程式碼已經說明了一切:

    procedure TTypeMarshaller<TSerial>.MarshalData(Data: TObject);

    但另外一個關鍵則是物件的欄位型態,前面筆者已經說過,TXXXXConverter類別,因此如果您要傳遞的物件的欄位型態是特別的型態,或是目前DataSnap 2010的TXXXXConverter類別沒有支援的型態,那麼就不能傳遞,當然要克服這很簡單,因為您可以自己寫一個客製化TXXXXConverter衍生類別,並且註冊給DataSnap 2010就可以了,這屬於進階的功能,以後有機會再討論吧。

    如何? DataSnap 2010的Marshaling/Unmarshaling功能是不是很強大又很容易使用? 各位讀者可以和上一篇我們自己傳遞TEmployee物件比較一下。善用DataSnap 2010的Marshaling/Unmarshaling功能可以讓我們輕易的開發出士又輕又快的跨平台分散式應用系統,成功的克服了以往COM/DCOM/COM+無法做到的事情,DataSnap 2010提供了目前最佳的分散式解決方案。

    我們下次再見,Have Fun!

    September 16

    JSON程式設計

    上篇文章簡單的介紹了如何使用DataSnap開發基於JSON分散式應用系統,Delphi 藉由原本的Midas/DataSnap和dbExpress的元件提供這個新的分散式計算能力,但使用這些元件都是資料的方式來存取遠端服務,然而我們也可以使用前面介紹的JSON類別再結合DataSnap來實作出存取遠端數值物件(Value Object)或是所謂的DTO(Data Transfer Object)的應用,讓用戶端可以存取遠端的物件。
    在下面的範例中我們將展示如何開發使用VO/DTO的DataSnap應用程式伺服器,並且在用戶端擷取其中封裝的資料和物件。這個範例將開發一個可以讓用戶端查詢資料和物件的DataSnap應用程式伺服器,當用戶端查詢物件時,我們將使用TJSONObject封裝物件並且傳遞到用戶端。
    X-3-1開發數值物件伺服器
    首先建立一個VCL Form應用程式專案,在它的主表單中加入如下的元件:
     

    圖7 VO DataSnap應用程式伺服器

    接著在專案中建立一個新的程式單元,於其中實作一個簡單的TEmployee類別如下:

    unit uEmployee;

    interface

    uses SysUtils, classes;

    type
      TEmployee = class
      private
        { Private declarations }
        FName : string;
        FEMail : string;
        FPhone : string;
      public
        { Public declarations }
        constructor Create(sName  : string = ''; sEMail : string = ''; sPhone : string = '');

        property Name : string read FName write FName;
        property EMail : string read FEmail write FEMail;
        property Phone : string read FPhone write FPhone;
      end;

    implementation

    { TEmployee }

    constructor TEmployee.Create(sName, sEMail: string; sPhone: string);
    begin
      FName := sName;
      FEmail := sEMail;
      FPhone := sPhone;
    end;

    end.

    再於專案中建立一個新的程式單元稱為uEmployeeValueObject,其中定義TEmployeeVO類別如下,請注意TEmployeeVO類別使用了
    {$MethodInfo ON}和{$MethodInfo Off}編譯器指令要求編譯器把TEmployeeVO類別的Reflection資訊編譯到執行檔中。

      {$MethodInfo ON}
      TEmployeeVO = class(TComponent)
      private
        { Private declarations }
        FEmployees : TObjectList<TEmployee>;

        function CreateEmployeeJSONObject(employee : TEmployee) : string;
        function CreateEmployeeJSONArray : string;
        function CreateEmployeeJSONObjectJ(employee : TEmployee) : TJSONObject;
      public
        { Public declarations }
        destructor Destroy; override;
        function GetEmployeeJ(const sName : string) : TJSONObject;
        function GetAllEmployeesJ : TJSONArray;
        procedure AddEmployee(const sName : string; const sEMail : string; const sPhone : string);
      end;
      {$MethodInfo Off}

    TEmployeeVO宣告了三個方法說明如下:

    方法名稱

    說明

    備註

    AddEmployee 讓用戶端呼叫增加員工資訊
    GetEmployeeJ 讓用戶端以員工姓名查詢員工物件 請注意GetEmployeeJ回傳TJSONObject型態的結果值,這代表範例應用程式將把員工物件封裝在TJSONObject物件之中並且回傳給用戶端
    GetAllEmployeesJ 讓用戶端查詢所有的員工資訊 查詢後端所有員工資訊,請注意GetAllEmployeesJ回傳型態為TJSONArray的結果值,這代表範例應用程式將把所有查詢結果封裝在TJSONArray物件中回傳


    下面小節將簡單的說明這些方法的實作。

    AddEmployee方法的實作
    AddEmployee方法非常的簡單,它從用戶端接受三個字串型態的參數,並且根據它們來建立TEmployee物件,最後再把建立的TEmployee物件加入在宣告為TObjectList<TEmployee>泛型型態的變數FEmployees之中。

    procedure TEmployeeVO.AddEmployee(const sName, sEMail, sPhone: string);
    var
      employee : TEmployee;
    begin
      if (FEmployees = nil) then
        FEmployees := TObjectList<TEmployee>.create(true);
      employee := TEmployee.Create(sName, sEmail, sPhone);
      FEmployees.Add(employee);
    end;

    GetEmployeeJ方法的實作
    GetEmployeeJ方法就非常的有趣了,它展示了如何於DataSnap應用程式伺服器中使用TJSONValue的相關類別,GetEmployeeJ回傳型態為TJSONObject的物件回用戶端。
    GetEmployeeJ根據用戶端傳遞來查詢的員工姓名,在Fem ployees中一一的搜尋具有相同名稱的TEmployee物件,找到之涕在013行呼叫CreateEmployeeJSONObjectJ來建立回傳的TJSONObject物件。

    001    function TEmployeeVO.GetEmployeeJ(const sName: string): TJSONObject;
    002    var
    003      ie : TList<uEmployee.TEmployee>.TEnumerator;
    004      employee : TEmployee;
    005    begin
    006      Result := nil;
    007      ie := FEmployees.GetEnumerator;
    008      while (ie.MoveNext) do
    009      begin
    010        employee := ie.Current;
    011        if (employee.Name = sName) then
    012        begin
    013          Result := CreateEmployeeJSONObjectJ(employee);
    014          break;
    015        end;
    016      end;
    017    end;

    CreateEmployeeJSONObjectJ方法根據找到的TEmployee物件來建立TJSONObject物件,它呼叫我們前面學習過的AddPair方法,把每一個TEmployee物件的特性名稱和特性值做為一個JSON的中『名稱/值』配對加入到TJSONObject物件。

    function TEmployeeVO.CreateEmployeeJSONObjectJ(employee: TEmployee): TJSONObject;
    var
      aJO : TJSONObject;
    begin
      aJO := TJSONObject.Create;
      aJO.AddPair(TJSONString.Create('姓名'), TJSONString.Create(employee.Name));
      aJO.AddPair(TJSONString.Create('EMail'), TJSONString.Create(employee.EMail));
      aJO.AddPair(TJSONString.Create('電話'), TJSONString.Create(employee.Phone));
      Result := aJO;
    end;

    GetAllEmployeesJ方法的實作
    GetAllEmployeesJ方法會把所有存在泛型型態變數FEmployees之中的TEmployee物件封裝在TJSONArray物件中回傳給用戶端。
    在013行仍然是呼叫CreateEmployeeJSONObjectJ為每一個CreateEmployeeJSONObjectJ建立一個TJSONObject物件,並且加入到回傳的TJSONArray物件中。

    001    function TEmployeeVO.GetAllEmployeesJ: TJSONArray;
    002    var
    003      ie : TList<uEmployee.TEmployee>.TEnumerator;
    004      employee : TEmployee;
    005      jo : TJSONObject;
    006      ja : TJSONArray;
    007    begin
    008      ie := FEmployees.GetEnumerator;
    009      ja := TJSONArray.Create;
    010      while (ie.MoveNext) do
    011      begin
    012        employee := ie.Current;
    013        jo := CreateEmployeeJSONObjectJ(employee);
    014        ja.AddElement(jo);
    015      end;
    016      Result := ja;
    017    end;

    最後不要忘記在主表單中註冊TEmployeeVO類別,如此一來用戶端才能看見並且呼叫TEmployeeVO輸出的方法:

    procedure TForm10.FormCreate(Sender: TObject);
    begin
      if DSServer1.Started then
        DSServer1.Stop;
      RegisterServers;
      DSServer1.Start;
    end;

    procedure TForm10.FormDestroy(Sender: TObject);
    begin
      DSServer1.Stop;
    end;

    procedure TForm10.RegisterServers;
    begin
      uEmployeeValueObject.RegisterServerClasses(Self, DSServer1);
    end;

    現在編譯並且執行DataSnap應用程式伺服器,並且準備開發用戶端。

    開發範例用戶端
    其實這個範例的重點就在於用戶端如何呼叫遠端支援TJSONValue相關類別的方法,這是因為在DataSnap 2009中無法支援TJSONValue相關類別,到了DataSnap 2010才支援。但是在筆者撰寫本章時Delphi 2010的Beta版仍然無法自動產生代表遠端類別的用戶端Proxy類別,因此想要呼叫前面實作的GetEmployeeJ和GetAllEmployeesJ方法,我們必須瞭解如何修改由Delphi產生的DataSnap用戶端類別。
    筆者認為當Delphi 2010正式版釋出時,CodeGear應該會把這個問題修正,如果您發現您使用的Delphi已經能夠產生正確的用戶端Proxy類別,那麼就不需要如下所敘述的修改了。
    首先建立一個VCL Form應用程式,放入TSQLConnection元件,設定Driver為DataSnap再把connected設定為True(請確定DataSnap應用程式伺服器已經在執行),接著用點選滑鼠右鍵,選擇Generate DataSnap client classes選項,儲存產生的程式單元為uServerProxy.pas,然後搜尋GetEmployeeJ方法,把012行修改為如下:

    (註, 筆者已經在Delphi 2010正式版中試過現在沒有問題了, 而筆者之所以沒把上述段落內容刪除是因為這段內容可以讓讀者更瞭解修改下面程式碼的意義, 即使讀者使用Delphi 2010正式版而不需要再修改下面的程式碼, 也可以把uServerProxy.pas開啟, 再看看由Delphi 2010產生的程式碼代表的意義)

    001    function TEmployeeVOClient.GetEmployeeJ(sName: string): TJSONObject;
    002    begin
    003      if FGetEmployeeJCommand = nil then
    004      begin
    005        FGetEmployeeJCommand := FDBXConnection.CreateCommand;
    006        FGetEmployeeJCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    007        FGetEmployeeJCommand.Text := 'TEmployeeVO.GetEmployeeJ';
    008        FGetEmployeeJCommand.Prepare;
    009      end;
    010      FGetEmployeeJCommand.Parameters[0].Value.SetWideString(sName);
    011      FGetEmployeeJCommand.ExecuteUpdate;
    012      Result := FGetEmployeeJCommand.Parameters[1].Value.GetJSONValue as TJSONObject;
    013    end;

    這是因為遠端GetEmployeeJ方法回傳TJSONObject型態的物件,因此我們需要把012行呼叫遠端方法執行的結果轉變型態為TJSONObject型態。
    同樣的,我們也需要修改GetAllEmployeesJ方法,轉變型態為回傳TJSONArray物件,如下所示:

    function TEmployeeVOClient.GetAllEmployeesJ: TJSONArray;
    begin
      if FGetAllEmployeesJCommand = nil then
      begin
        FGetAllEmployeesJCommand := FDBXConnection.CreateCommand;
        FgetAllEmployeesJCommand.CommandType := TDBXCommandTypes.DSServerMethod;
        FGetAllEmployeesJCommand.Text := 'TEmployeeVO.GetAllEmployeesJ';
        FGetAllEmployeesJCommand.Prepare;
      end;
      FGetAllEmployeesJCommand.ExecuteUpdate;
      Result := FGetAllEmployeesJCommand.Parameters[0].Value.GetJSONValue as TJSONArray;
    end;

    瞭解了如何以及為什麼需要修改DataSnap用戶端類別之後,我們就可以開始實作呼叫遠端方法的程式碼了。
    首先下面的程式碼藉由自動產生的TEmployeeVOClient類別呼叫AddEmployee方法,把用戶端輸入的員工資料加入在遠端的應用程式伺服器中:

    procedure TForm11.btnAddEmployeeClick(Sender: TObject);
    var
      evo : TEmployeeVOClient;
    begin
      evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection);
      try
        evo.AddEmployee(edtAddName.Text, edtAddEMail.Text, edtAddPhone.Text);
        edtAddName.Text := '';
        edtAddEMail.Text := '';
        edtAddPhone.Text := '';
      finally
        evo.Free;
      end;
    end;

    下圖是執行範例用戶端應用程式並且點選『增加員工』按鈕以執行上述程式碼的畫面:


    圖8 用戶端應用程式呼叫遠端AddEmployee方式加入員工資訊

    接著是藉由TEmployeeVOClient呼叫GetEmployeeJ查詢員工資料的實作程式碼:
    procedure TForm11.btnQueryEmployeeClick(Sender: TObject);
    var
      evo : TEmployeeVOClient;
      jo : TJSONObject;
      employee : TEmployee;
    begin
      evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection);
      try
        jo := evo.GetEmployeeJ(edtQname.Text);
        employee := TEmployee.Create(jo.Get(0).JsonValue.ToString, jo.Get(1).JsonValue.ToString, jo.Get(2).JsonValue.ToString);
        Self.edtQEMail.Text := employee.EMail;
        Self.edtQPhone.Text := employee.Phone;
      finally
        FreeAndNil(employee);
        FreeAndNil(jo);
        evo.Free;
      end;
    end;

    在上面的程式碼中呼叫GetEmployeeJ取得代表員工的TJSONObject物件,再根據TJSONObject物件中的資訊於用戶端建立TEmployee物件,再顯示員工資訊於表單中,最後不要忘記釋放TEmployee物件,TJSONObject物件和TEmployeeVOClient物件。
    下圖是先增加李維這筆員工資料之後,再輸入李維來查詢的畫面:
     

    圖9 輸入員工姓名查詢員工資料
    點選上圖中的『查詢單一員工』按鈕之後我們的確可以取得遠端員工物件的資訊,如下圖所示:

     
    圖10 查詢的結果畫面

    但是為什麼上圖中的員工資訊,例如員工的Email有引號包圍呢? 這當然是因為這是JSON封裝字串的規範,而在上面的程式碼中當我們建立TEmployee物件時是直接呼叫ToString方法,如果我們不希望TEmployee物件的特性值有引號包圍,那麼我們可以修改程式碼如下,改呼叫Value方法:
        employee := TEmployee.Create(jo.Get(0).JsonValue.Value, jo.Get(1).JsonValue.Value, jo.Get(2).JsonValue.Value);

    那麼就會有如下正確的結果:

     
    圖11 查詢的結果畫面
    最後讓我們看看如何查詢所有的員工資料。下面的程式碼藉由TEmployeeVOClient呼叫GetAllEmployeesJ取得TJSONArray物件,然後進入for迴圈把其中的每一個元素取出再轉變型態為TJSONObject物件,最後再根據TJSONObject物件一一的建立用戶端的員工物件。

    procedure TForm11.btnQueryAllEmployeesClick(Sender: TObject);
    var
      evo : TEmployeeVOClient;
      jo : TJSONObject;
      ja : TJSONArray;
      employee : TEmployee;
      iIndex: Integer;
    begin
      evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection);
      try
        ja := evo.GetAllEmployeesJ;
        for iIndex := 0 to ja.Size - 1 do
        begin
          jo := ja.Get(iIndex) as TJSONObject;
          employee := TEmployee.Create(jo.Get(0).JsonValue.ToString, jo.Get(1).JsonValue.ToString, jo.Get(2).JsonValue.ToString);
          Memo1.Lines.Add(employee.Name);
          FreeAndNil(employee);
        end;
      finally
        FreeAndNil(ja);
        FreeAndNil(employee);
        evo.Free;
      end;
    end;

    下圖是執行此查詢的結果,我們可以看到用戶端的確可以查詢到遠端的所有員工的資訊。

     
    圖12 所有員工資料查詢的結果畫面
    當然,我們一樣可以修改上面的程式碼如下:
          employee := TEmployee.Create(jo.Get(0).JsonValue.Value, jo.Get(1).JsonValue. Value, jo.Get(2).JsonValue. Value);

    那麼我們會看到如下的結果:


    圖13 所有員工資料查詢的結果畫面

    本章敘述了Delphi VCL框架中支援JSON開發的相關類別,讀者從本章中可以瞭解這些不但是依照JSON規範設計的,而且非常的直覺,好用,只要我們對於JSON規範有基本的掌握就可以順利的使用它們。

    此外,新版的DataSnap不但使用JSON做為封裝和傳遞資料的基礎,現在也加入支援使用TJSONValue和衍生類別做為遠端方法的參數和回傳值,如此一來可以讓開發人員撰寫客製化的JSON應用,例如使用VO/DTO設計樣例來傳遞資料和物件,使用這樣的技術,開發人員也可以使用.NETJava或是任何支援JSON的程式語言來開發用戶端應用程式了。

     練完2篇文章的基本功之後, 在下篇文章開發, 我們將介紹如何使用Delphi 2010新的DataSnap Server精靈來大幅簡化開發的工作, 並且會介紹DataSnap強大的Marshaling和unMarshaling的功能,Marshaling和unMarshaling將可以讓我們使用JSON傳遞物件變得非常簡單,使用Marshaling和unMarshaling也可以讓上面傳遞TEmployee物件的程式碼大幅減少. 我們下次再見, Have FUN!



    September 11

    Delphi 2010 JSON程式設計

    從Delphi 2009開始Delphi便開始支援JSON,DataSnap 2009開始使用JSON做為開發分散式應用系統的基礎技術,開始逐漸捨棄使用COM/DCOM/COM+。由於JSON具備跨平台,跨程式語言和跨工具的特性,因此DataSnap在結合JSON和dbExpress之後也逐漸具備了跨平台的基礎。但是Delphi 2009對於JSON的支援只限於使用在DataSnap之中,因此如果開發人員需要進行通用的JSON程式設計,Delphi 2009在這方面仍然顯得不足。

    到了Delphi 2010情形開始改變,因為VCL框架開始內建支援JSON的類別,因此Delphi的開發人員就可以利用這些和JSON相關的類別來進行JSON程式設計的工作。本章的重點就是討論VCL框架對於JSON的支援,本章將討論如何使用VCL框架中和JSON相關的類別來進行JSON開發的工作。不過在討論這些類別之前,我們需要先簡單的介紹什麼是JSON。

    X-1 JSON是什麼?

    簡單的說,JSON是一種資料封裝格式以及資料交換格式,JSON是JavaScript Object Notation的縮寫,它是一種輕量級的資料交換格式,易於一般人閱讀和編寫,同時也易於機器解析和生成。

    JSON是基於JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一個子集,JSON採用完全獨立于語言的文字格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等),這些特性使JSON成為理想的資料交換語言。

    JSON是由下面的兩個基本架構組成的:

    • “名稱/值”對的集合(A collection of name/value pairs)。在不同的程式語言中,這個架構經常以物件(object),紀錄(record),結構(struct),字典(dictionary),雜湊表(hash table),有鍵列表(keyed list),或者關聯陣列 (associative array)來實作。
    • 值的有序列表(An ordered list of values)。在大部分的程式語言中,這個架構經常使用陣列(array)來實作。

    OK,看了上述的定義之後,讀者可能還是覺得模模糊糊,也許讓我們使用實際的範例來說明可以讓讀者更容易瞭解。

    假設現在我們有一個DataSnap伺服器,它使用JSON和用戶端進行資料交換的工作,那麼現在用戶端向這個DataSnap伺服器查詢筆者所撰寫的書籍,例如本書『實戰Delphi 2010』,那麼如何把這個資訊以JSON的格式傳遞給用戶端呢?

    首先讓我們觀察下圖,在JSON中物件的代表方式是以“{”開始,以“}”結束,另外在前面我們也說明JSON是以『名稱/值』的格式來代表資料,因此從下圖中我們可以看到名稱和值之間是以“:”符號分隔,其中『名稱』是字串格式,而值則是以數值格式來代表:

     



    正在更新標籤...

    圖1 JSON封裝物件的格式

    在JSON規範中上圖的字串規範如下圖所示:




    正在更新標籤...


    圖2 JSON封裝字串的格式

    從上圖中我們可以知道在JSON中字串是以”開始,以”結束,可包含Unicode的字元組。而圖1中的數值格式則如下圖所示:




    正在更新標籤...

    圖3 JSON封裝數值的格式

    從圖3中可知,JSON的數值可以是字串,數字,物件,陣列或是true/false/null值。由於JSON的數值可以是物件或是陣列,而物件又可以包含字串:數值配對,因此JSON規範可以封裝任何複雜的資料。

    有了上述對於JSON基本的瞭解之後,我們就可以使用如下的格式從DataSnap伺服器傳遞資料到用戶端:

    {“書名”:”實戰Delphi 2010”}

    從這個格式中我們可以知道這是用JSON中封裝物件的規則,把『字串:數值』配對包含在{和}符號之中。

    如果我們希望也傳遞作者的資訊,那麼可以使用逗號分離每一個『字串:數值』配對,如下所示,讀者也可以回頭再參考圖1就可以發現這是圖1的規則。

    {“書名”:”實戰Delphi 2010”,“作者”:”李維”}

    如果現在再把書籍出版年份也傳遞,那麼就可以使用下面的格式:

    {"書名":"實戰Delphi 2010","作者":"李維","出版年份":2010}

    請注意的是,出版年份的數值是2010這個數字,因為如圖3所示數值可以是數字(number),而在JSON中數字的定義如下:



    正在更新標籤...

     

    圖4 JSON封裝數字的格式

    從圖4我們可以知道,在JSON中數字可以是整數或是浮點數。

    {"書名":"實戰Delphi 2010","作者":"李維","出版年份":2010}

    因此如果我們再傳遞書籍價格,那麼可以使用如下的格式:

    {"書名":"實戰Delphi 2010","作者":"李維","出版年份":2010,"價格":45.95}

    最後如果我們再加入書籍已出版否資訊,那麼可以使用如下的格式:

    {"書名":"實戰Delphi 2010","作者":"李維","出版年份":2010,"價格":45.95,"已出版否":false}

    從上面的範例中我們可以看到JSON規範如何封裝各種不同型態的資料。那麼如果我們需要一次傳遞多筆資料到用戶端的話,又要如何封裝呢?這可以使用JSON中陣列的規範。

    圖5是JSON陣列的封裝規則,陣列“[”開始,以“]”結束,而陣列中的每一個元素都是數值,每一個元素使用逗號分隔。回頭參考圖3 JSON封裝數值的格式,由於數值可以是物件,因此我們如果需要一次傳遞多筆資料到用戶端,那麼就可以使用JSON的陣列來封裝。




    正在更新標籤...

    圖5 JSON封裝陣列的格式

    例如下面就是使用JSON的陣列封裝性三個代表書籍的物件,陣列中的每一個元素數值是物件,每一個元素數值使用逗號分隔:

    [{“書名”:”實戰Delphi 2010”},{“書名”:”Inside VCL”},{“書名”:”Borland傳奇”}]

    如何? 一旦瞭解了JSON封裝資料的規則之後讀者是不是覺得使用JSON非常的簡潔呢? 正由於JSON在封裝資料的規則比較簡單,因此在解析JSON資料時速度也比較快,JSON的簡潔規則讓JSON擁有較少的資料流量和較快的處理速度,因此讓JSON在Web應用方面佔有優勢,也愈來愈受歡迎,這也是DataSnap現在選擇使用JSON做為基礎技術的原因。

    在離開本小節之前,讓我們比較一下使用XML和JSON在封裝和交換資料方面的差異。下面是使用XML封裝一個員工的資訊:

    <?xml version="1.0" encoding="utf-8"?>

    <user>

    <name>李大明</name>

    <password>123456</password>

    <department>R&D</department>

    <gender>男</gender>

    <age>26</age>

    </user>

    如果我們使用JSON來封裝的話,那麼就如下所示:

    {

    "name":"李大明",

    "password":"123456",

    "department":"R&D",

    "gender":"男",

    "age":"26"

    }

    我們可以看到JSON使用了較XML少的資料量來封裝相同的資訊,此JSON在資料交換方面擁有比較多的優勢。

    讀者可以參考www.json.org以瞭解更多有關JSON的資訊。

    在對於JSON有了基本的瞭解之後,更重要的是我們需要知道如何在Delphi中進行JSON的程式設計。

    X-2 VCL框架中支援JSON的類別

    Delphi在2009開始使用JSON做為DataSnap 2009封裝和傳遞資料的格式,但是Delphi 2009對於JSON的支援和DataSnap的功能撰寫的非常緊密,開發人員除了能夠在DataSnap 2009中使用JSON之外,並不容易使用Delphi 2009來開發其他應用型態的JSON應用程式。到了Delphi 2010這個現象獲得了大幅的改善,Delphi 2010提供了許多通用的JSON相關類別,開發人員可以使用這些通用的JSON相關類別開發任何型態的JSON應用程式而不只限於DataSnap應用程式。

    Delphi 2010在新的DBXJSON.pas程式單元中提供了這些JSON相關的類別,如果讀者仔細觀察前面JSON規則圖的話,就可以發覺如果我們需要使用類別來實作支援JSON的規範,那麼我們我們至少需要下面的三個類別:

    類別                 說明

    JSONValue 用來代表和實作圖3的實體和關係

    JSONPair         用來代表JSON規範中的『名稱/值』配對實體和關係

    JSONObject 用來代表和實作圖1的實體和關係

    JSONString 用來代表和實作圖2的實體和關係

    JSONArray 用來代表和實作圖5的實體和關係

    JSONNumber 用來代表和實作圖4的實體和關係

    當然在上述的基礎類別中還存在繼承的關係,例如由於JSON規範中數值可以代表字串,物件,陣列等實體,因此上面的JSONObject,JSONString等類別可以從JSONValue類別繼承下來。

    Delphi 2010中支援JSON等相關類別就是使用這個觀念實作出來的,因此只要讀者瞭解了前面解釋JSON規範的觀念,就可以非常直覺的瞭解這些實作類別。在下面的表格中整理了這些類別,並且提供了簡單的敘述:

    類別                 說明

    TJSONAncestor 抽象類別,是所有JSON相關類別的根類別

    TJSONPair 實作JSON規範中『名稱/值』配對的類別

    TJSONValue 實作JSON規範中數值的類別,TJSONValue是TJSONAncestor的繼承類別

    TJSONTrue 實作JSON規範中代表True的類別,從TJSONValue繼承下來

    TJSONString 實作JSON規範中代表字串的類別,從TJSONValue繼承下來

    TJSONNumber 實作JSON規範中代表數值的類別,它從TJSONString繼承下來,因為JSON使用文字格式傳遞封裝和傳遞資料,因此所有數值都將轉換為文字字串型態傳遞,到達目的地之後再反轉回數值

    TJSONObject 實作JSON規範中代表物件的類別,從TJSONValue繼承下來

    TJSONNull 實作JSON規範中代表Null的類別,從TJSONValue繼承下來

    TJSONFalse 實作JSON規範中代表False的類別,從TJSONValue繼承下來

    下圖是這些類別之間的繼承和關連關係圖,請讀者對照下圖和以前圖1到圖5的JSON規範,就可以瞭解這些類別的實作是完全從JSON規範中設計出來的,簡單又符合直覺:

     

    圖6 Delphi中支援JSON的類別架構

    接下來讓我們簡單的說明這些類別提供的服務,如此一來讀者就可以瞭解如何在程式碼中使用它們。

    X-2-1 TJSONAncestor類別

    TJSONAncestor類別是所有JSON相關類別的根類別,它主要定義了三個虛擬方法讓它的衍生類別來複載實作,下面的表格敘述了這些虛擬方法:

    虛擬方法                                                                 說明

    function ToString: UnicodeString; virtual;                 ToString虛擬方法會把JSON的內容轉換為文字字串形式回傳,TJSONAncestor的衍生類別需要實作這個虛擬方法,例如TJSONObject類別會實作這個虛擬方法並且把物件內容轉換為字串的形態回傳

    function EstimatedByteSize: Integer; virtual; abstract; EstimatedByteSize虛擬方法回傳JSON物件預估的位元組大小,TJSONAncestor的衍生類別需要實作這個虛擬方法,每一個衍生類別都會回傳它本身需要佔據位元組的大小。EstimatedByteSize虛擬方法的目的是在封裝和傳遞JSON物件時程式碼能夠配置足夠的記憶體大小之用。

    function ToBytes(const Data: TBytes; const Offset: Integer): Integer; virtual; abstract; ToBytes虛擬方法能夠把JSON物件以位元組的形式回傳,TJSONAncestor的衍生類別需要實作這個虛擬方法,每一個衍生類別都會在這個複載的虛擬方法中把自己轉換為位元組形態。

    TJSONAncestor本身是一個抽象類別,因此開發人員並不應該直接在程式碼中使用它,而是應該使用下面介紹的衍生類別。

    X-2-2 TJSONValue類別

    TJSONValue類別本身也是一個抽象類別,它是直接從TJSONAncestor繼承下來的,TJSONValue類別只是做為一個Placeholder,它主要的目的是代表圖3中JSON規範value的概念,因此它的定義非常簡單,如下所示:

      TJSONValue = class abstract(TJSONAncestor)

      end;

    稍後介紹的許多類別都是從TJSONValue繼承下來的,幾乎和圖3顯示的規範架構一樣。

    X-2-3 TJSONPair類別

    TJSONPair類別是實作JSON規範中『名稱/值』配對的類別,它從TJSONAncestor直接繼承下來,由於TJSONPair是實作『名稱/值』配對,因此它提供了兩個最重要的特性,JsonString和JsonValue:

        property JsonString: TJSONString read GetJsonString write SetJsonString;

        property JsonValue: TJSONValue read GetJsonValue write SetJsonValue;

    JSonString是代表『名稱/值』配對中名稱的特性,而JsonValue則代表其中值的特性。請注意的是JsonValue的型態是TJSONValue,而根據前面表格所敘述,TJSONValue可以代表任何從TJSONValue繼承下來的類別,因此JsonValue可以代表TJSONString,TJSONNumber或是TJSONObject等。

    那麼如何建立TJSONPair呢? TJSONPair定義了四個複載的建構函式,其中最重要的三個如下所示:

        constructor Create(const Str: TJSONString; const Value: TJSONValue); overload;

        constructor Create(const Str: UnicodeString; const Value: TJSONValue); overload;

        constructor Create(const Str: UnicodeString; const Value: UnicodeString); overload;

    上面第一個建構函式可以藉由TJSONString和TJSONValue物件來建立TJSONPair物件,第二個建構函式可以使用Unicode字串和TJSONValue物件來建立TJSONPair物件,至於第3個建構函式則是使用兩個Unicode字串來建立TJSONPair物件。

    例如,如果我們要建立如下的JSON『名稱/值』配對:

    “書名”:”實戰Delphi 2010”

    那麼我們可以使用如下的程式碼:

    var

      jp : TJSONPair;

    begin

      jp := TJSONPair.Create(TJSONString.Create('書名'), TJSONString.Create('實戰Delphi 2010'));

      try

        Memo1.Lines.Add(jp.ToString);

      finally

        jp.Free;

      end;

    上面的程式碼使用了第一個建構函式建立TJSONPair物件,TJSONPair類別的ToString方法可以把JSON物件之中的內容以文字字串形式回傳,最後釋放TJSONPair物件時,TJSONPair物件會自動釋放傳入建構函式之中的兩個TJSONString物件。

    當然您也可以使用第二個建構函式來建立TJSONPair物件,如下所示:

      jp := TJSONPair.Create('書名', TJSONString.Create('實戰Delphi 2010'));

    或是使用第3個建構函式,因為在這個範例中『名稱/值』配對中的名稱和值都是字串:

      jp := TJSONPair.Create('書名', '實戰Delphi 2010');

    當然,如果您需要建立如下的JSON『名稱/值』配對:

    "價格":45.95

    由於值是數字,因此我們只能使用第一個或是第二個建構函式來建立:

      jp := TJSONPair.Create(TJSONString.Create('價格'), TJSONNumber.Create(45.95));

    或是:

      jp := TJSONPair.Create('價格', TJSONNumber.Create(45.95));

    一旦建立了TJSONPair物件,我們也可以藉由存取它的JsonString和JsonValue特性來存取『名稱/值』配對中的名稱或是值了,例如:

        Memo1.Lines.Add('JSONString : ' + jp.JsonString.ToString);

        Memo1.Lines.Add('JSONValue : ' + jp.JsonValue.ToString);

    X-2-4 TJSONString類別

    TJSONString是從TJSONValue類別繼承下來,它是使用來代表Unicode字串的資料,它可以是『名稱/值』配對中的名稱或是值或是兩者。

    TJSONString最重要的方法應該是它的建構函式了,它接受一個Unicode字串做為參數:

        constructor Create(const Value: UnicodeString); overload;

    在Unicode字串使用建立TJSONString物件之後,如果開發人員需要加入額外的字元,那麼可以呼叫AddChar虛擬程序,AddChar會在原本的Unicode字串之後加入參數Ch的字元內容。

        procedure AddChar(const Ch: WideChar); virtual;

    如果開發人員需要TJSONString物件中字串的內容值,可以呼叫ToString:

        function ToString: UnicodeString; override;

    如果開發人員只是需要TJSONString物件中字串的內容值,而不需要額外的開始”符號以及結束的”符號,那麼可以呼叫TJSONString的Value函式

        function Value: UnicodeString; override;

    例如下面的程式碼:

    var

      js : TJSONString;

    begin

      js := TJSONString.Create('實戰Delphi 2010');

      try

        Memo1.Lines.Add('TJSONString.ToString : '+ js.ToString);

        Memo1.Lines.Add('TJSONString.Value : '+ js.Value);

      finally

        js.Free;

      end;

    下面是呼叫ToString和Value的差異:

    TJSONString.ToString : "實戰Delphi 2010"

    TJSONString.Value : 實戰Delphi 2010

    X-2-5 TJSONObject類別

    TJSONObject類別從TJSONValue直接繼承下來,它代表JSON規範中封裝物件的類別。要建立TJSONObject物件非常簡單,只需要呼叫它的建構函式即可:

        constructor Create;

    那麼如果我們需要建立如下的JSON物件內容,那麼應該如何做呢?

    {“書名”:”實戰Delphi 2010”}

    請注意上面的結構,其實是在JSON物件之中包含一個TJSONPair物件,因此我們只需要執行下面的步驟即可:

    • 建立TJSONObject物件
    • 建立TJSONPair物件
    • 把TJSONPair物件加入到TJSONObject物件

    要把TJSONPair物件或是JSON『名稱/值』配對加入到TJSONObject物件中,我們可以使用TJSONObject類別中下面兩個複載的AddPair方法:

        procedure AddPair(const Pair: TJSONPair); overload; virtual;

        procedure AddPair(const Str: TJSONString; const Val: TJSONValue); overload; virtual;

    因此如果我們需要建立如下內容的TJSONObject物件:

    {"書名":"實戰Delphi 2010","出版年份":2010}

    那麼可以使用如下的程式碼:

    var

      jo : TJSONObject;

      jp : TJSONPair;

    begin

      jo := TJSONObject.Create;

      try

        jp := TJSONPair.Create('書名', '實戰Delphi 2010');

        jo.AddPair(jp);

        jo.AddPair(TJSONString.Create('出版年份'), TJSONNumber.Create(2010));

        Memo1.Lines.Add(jo.ToString);

      finally

        jo.Free;

      end;

    同樣的TJSONObject類別的ToString方法可以把其中的內容以文字字串型態回傳:

        function ToString: UnicodeString; override;

    X-2-5 TJSONNumber類別

    TJSONNumber類別是從TJSONString類別繼承下來的,這是因為在JSON規範中整數和浮點數都必須使用字串形式來代表,由於TJSONString已經提供了使用字串形式來代表JSON內容的功能,因此TJSONNumber只需要從它繼承下來並且把整數和浮點數轉換為文字字串型態即可。

    在前面我們看過多次建立TJSONNumber物件的範例了,它的建構函式接受一個double值的參數:

        constructor Create(const Value: Double); overload;

    一旦建立了TJSONNumber物件,呼叫它的ToString方法即可取得其內容。

    在離開本小節之前再讓我們看看三個剩下簡單的類別,它們是TJSONTrue,TJSONFalse和TJSONNull。這三個類別分別實作圖3中的true,false和null三個JSON數值。例如我們如果需要下面的JSON內容:

    {"書名":"實戰Delphi 2010","出版否":false, “撰寫中”:true, “出版商”:null}

    那麼我們使用下面的程式碼即可:

    var

      jo : TJSONObject;

      jp: TJSONPair;

    begin

      jo := TJSONObject.Create;

      try

        jp := TJSONPair.Create('書名', '實戰Delphi 2010');

        jo.AddPair(jp);

        jp := TJSONPair.Create('書名', TJSONFalse.Create);

        jo.AddPair(jp);

        jp := TJSONPair.Create('撰寫中', TJSONTrue.Create);

        jo.AddPair(jp);

        jp := TJSONPair.Create('出版商', TJSONNull.Create);

        jo.AddPair(jp);

        Memo1.Lines.Add(jo.ToString);

      finally

        jo.Free;

      end;

    在讀者瞭解了如何使用這些JSON相關的類別之後,讓我們看看如何使用它們在分散式應用系統之中。




    Delphi 2010產品技術發表會投影片和範例檔!

    本來昨天(星期四)就應該把2個壓縮檔放上來, 但由於昨天我一直無法連上我的部落格, 到今天連上我的部落格才發現MS的部落格不能上傳檔案, 只能上傳相片, 哇勒, 而以前CSDN提供給我的地方也不能用了, 沒想到現在連個棲身之地都沒了, 因此我得再找其他地方, 等我找到地方上傳之後再公布下載位址吧, 在這裡向各位說一聲抱歉. 


    OK, 我已經把Delphi 2010產品技術發表會投影片和範例檔給了興德請興德放在網上讓各位下載, 各位可以在下面的地址找到(噢, 我現在才發現他們把我的英文名字也拼錯了, 哇勒是Gordon不是Golden啊):

    http://www.sinter.com.tw/codegear/codegear_technique.html#golden_book

    September 07

    RAD Studio 2010背後的變化!

    Embarcadero終於在2009年的8 月底完成了RAD Studio 20102010版是Embarcadero接手DelphiC++BuilderRAD Studio之後的第一個自行研發的版本,出乎我很意外的,2010版完全沒有延遲,不像在Borland的時期,每一個Delphi/C++Builder一定至少會延遲2個星期到一個月左右。從這次Delphi/C++Builder 2010按時推出可以說明Delphi/C++Builder研發團隊在Embarcadero終於獲得了合理的資源,而且從Delphi/C++Builder 2010推出之後,仔細的研究2010VCLRTL以及和R&D的人對話之後,我發覺Delphi/C++Builder 2010是一個非常重要的版本,因為許多未來Delphi/C++Builder的發展方向都從2010版默默的展開了。

    首先Delphi/C++Builder 2010在編譯器進行了許多的改善,除了持續強化2009版就出現的泛型程式設計和匿名方法之外,2010的編譯器開始提供更豐富的RTTI元資料以準備提供類似Java/C#Reflection功能,2010也提供了背景編譯,允許開發人員在IDE中一邊持續工作,一邊在背景編譯專案,此外另外有兩個同時並行的編譯器團隊,一組在做Delphi/C++Builder 201064位元編譯器,另一組在做跨平台的Delphi編譯器。另外32位元的編譯器小組也在研究未來第3方元件能夠以二進行形式直接昇級到新版本而無需元件的原始程式,明年應該就可以看到64位元或是跨平台的編譯器以及新的最佳化C++Builder編譯器所以編譯器快準備好了。

    dbExpress早已經好跨平台的準備,因為現在所有的dbExpress相關程式碼都可以使用Delphi來撰寫,此外藉由Delphi編譯器提供更豐富的RIIT資訊,Delphi/C++Builder團隊開始研發OR Mapping的能力。這個新的OR Mapping功能也應該是跨平台的,因為它也將整合到VCL了,所以dbExpressVCL框架和快準備好了。

    再看Delphi/C++Builder的分散式技術,一直以來Delphi/C++Builder都是使用Windows平台上的分散式技術做為基礎,從Midas使用的COM,到DCOM/COM+,這一塊一直不容易改變,還好在JSON逐漸取得廣泛的應用後,Delphi/C++Builder2009版開始支援JSON。只是Delphi/C++Builder 2009JSONdbExpress綁的太緊,無法輕易的使用Delphi/C++Builder來開發一般的JSON應用程式。到了Delphi/C++Builder 2010Delphi/C++Builder團隊終於把JSON的能力封裝在數個易於使用的類別中,讓開發人員終於可以使用Delphi/C++Builder 2010進行通用的JSON開發,此外又加入了非同步以及過濾器的能力,讓Delphi/C++Builder 2010可以基於JSON開發分散式應用系統,終於打通了跨平台分散式架構的任督兩脈,DataSnap也終於可以實作在Mac OSLinux平台中。

    再看看Delphi/C++Builder 2010火熱的觸碰/手勢技術,雖然目前只能使用在Window XPVistaWindows 7中,但是未來Delphi將提供跨平台和移動運算的能力,那麼觸碰/手勢如何使用在其他平台和移動移動呢?

    如果去研究Delphi/C++Builder 2010如何實作觸碰/手勢技術,那麼我們會驚訝的發現,Delphi/C++Builder團隊在研發觸碰/手勢技術時已經考慮到了未來把觸碰/手勢技術移植到其他平台的能力,例如如果我們搜尋TGestureEngine類別,就可以發現下面的實作程式碼:

      TGestureEngine = class(TCustomGestureEngine)

      strict private

        class var FDefaultEngineClass: TGestureEngineClass;

        class var FDefaultRecognizerClass: TCustomGestureRecognizerClass;

        class var FRecognizer: TCustomGestureRecognizer;

    哈,注意看看這個實作程式碼,目前在Windows平台上實作的觸碰/手勢技術只是內定(Default)的類別,因為它使用了class var來宣告FDefaultEngineClassFDefaultRecognizerClass,以及FRecognizer,這種設計很明顯的是一個plug-in設計樣例,未來當Delphi/C++Builder團隊在其他平台,例如手機的移動平台,實作了觸碰/手勢技術,那麼只需要註冊新的引擎(Engine)Recognizer,再取代Windows平台的引擎(Engine)Recognizer,那麼跨平台的觸碰/手勢技術就實現了。

    最後回到RTLDelphi/C++BuilderFastCode專案仍然在持續進行中,如果您在Delphi/C++Builder 2010中搜尋就可以看到更多FastCode專案的成果,此外Delphi/C++Builder團隊也開始在RTL中加入跨平台的多執行緒函式庫,打造跨平台的高效RTL

    然而我仍然希望看到Delphi/C++Builder快點支援RIA的開發,Delphi/C++Builder 2010開始支持RESTful架構令人喜出望外,但我希望再上一層樓,結合RESTfulRIAWeb技術為一體。

    Delphi/C++Builder 2010在表面上呈現了許多非常實用又吸引人的新功能,而且許多的新功能都是領先業界的,不過如果您進入到Delphi/C++Builder 2010的實作原始程式世界,您會發現更多令人興奮的東西。Delphi/C++Builder團隊在擁有了足夠的研發資源之後,的確可以從Delphi/C++Builder  2010再次感受到如同Delphi 5Delphi 7時期蓬勃發展的精神。