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框架無限的發展潛能。
好了,時間已晚,我們也下次再見了。