2010 年 11 月 的封存

回顧過去,迎向未來!

不知怎麼搞的,最近經常想起10多年前Delphi在台灣市場是如何興起和壯大的,往事歷歷不斷的在我腦海中回憶和浮現。我想這也應該不是偶然的,這應該是因為最近我在各地進行Delphi和C++Builder的活動旅途中不斷重覆的聽到客戶幾乎相同的意見和抱怨,最常聽到的便是沒有中文資料,沒有中文書籍,初學者入門困難等,反而前幾年抱怨品質不穩的次數愈來愈少,由此看來Delphi/BCB最新版本的品質的確是在進步中,解決了許多Borland/CodeGear未期的問題。

日前我到印尼為Embarcadero進行RAD Studio的產品發表,由於我們和當地的一位作者合作,而這位印尼作者正在撰寫2本Delphi XE的書籍,而且在產品發表會當場宣佈他要成立使用者俱樂部,因此當場讓所有參加的人反應熱烈,所有的人都要參加而且都要購買這位印尼作者即將出版的Delphi書籍,而且在產品發表會結束之後久久不散,不停的圍繞者我們詢問問題以及Embarcadero以後會如何經營印尼市場。這場產品發表會深深的讓我瞭解到Delphi/BCB的客戶是如何的渴望相關的資料和書籍。

台灣市場也反映了極為類似的需求,我以及許多客戶也都向Embarcadero反映了不知多少次,希望能夠解決這個問題,無奈台灣並不像當初台灣Borland擁有較多的資源可以自己進行中文手冊的翻譯和開發的工作。然而在努力之下,事情似乎有了轉機,因為日前和以前台灣Borland的一些老朋友聊天,大家都覺得在台灣Delphi和BCB仍然擁有許多的客戶,也有許多的系統需要昇級使用最新的技術,例如64位元系統,JSON和REST架構的應用系統,因此這些台灣Borland的老骨頭決定動手做一些事情來幫助Delphi和BCB的客戶。

在12月15日應該會有一個發表會,邀請一些台灣Borland的老朋友和老客戶和我們敘敘舊並且談談未來Delphi/BCB的技術發展方向,在中文資料方面這些台灣Borland的老骨頭準備先把我的一些舊的書籍昇級到最新的XE版,建立中文支援網站和論壇,再準備出一系列的新技術中文資料,例如:

  • dbExpress程式設計
  • DataSnap+程式技術
  • 如何開發REST應用系統
  • JSON程式設計
  • VCL For Web程式設計
  • 泛型程式設計
  • Delphi程式語言新功能
  • 結合開發和FinalBuilder,AQTime等
  • 其他主題的Delphi/BCB程式設計….

也許我們無法立刻把所有的中文資料做出來,但這總是一個好的開始,台灣Borland的老骨頭們跨出了第一步,他們的夢想是希望能夠再次把Delphi/BCB興盛起來,我不知道他們能不能成功,但我知道這一定能夠幫助台灣的Delphi/BCB客戶,祝他們成功,最後我附上了數年前台灣Borland為Delphi第一次自己翻譯,開發的手冊的像片(我仍然珍藏著這本手冊),做為回顧過去,迎向未來的記念吧。

 

廣告

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 則迴響