2024年4月20日发(作者:)

Delphi 2010 DataSnap白皮书1

2010-04-07 11:25

在这个白页中我们将讲解Delphi2010 DataSnap架构新的特性和功能.

1. DATASNAP 历史

作为MIDAS起始于Delphi3,Delphi4是MIDAS II,Delphi5中是MIDASIII,而

后基于COM远程数据模块方式使

用TCP/IP,HTTP,(D)COM构建出强大的通讯能力.从Delphi6开始改名为

DataSnap,直到D2007这个框架一直在使

用.D2009重新架构了DataSnap—移除COM依赖,使用TCP/IP以更轻量级的方式

生成远程服务对象和客户端连接

能力.同时提供了与Delphi Prism2009开发的.NET程序通讯的功能.

Delphi2010中构建于D2009架构之上,并对此架构做了进一步的扩展,包括使用

两个向导来创建新的

部署目标(VCL窗体,Window服务,控制台及面向Web的ISAPI,CGI或Web App

Debugger).HTTP(S)传输协

议,HTTP验证,客户端回调函数,REST和JSON的支持,及使用过滤器来支持压缩和

解压缩.

1.1 DATASNAP范例数据位置

本白页中我建议您使用Demo和范例来学习.虽然Delphi支持很多数据库系统,

使用DBX4,ADO dbGo,或

其他数据存取技术,为了演示方便我这里使用DBX4来操作BlackfishSQL的

数据库.见

[ C:Documents and SettingsAll UsersDocumentsRAD

].在截图

中可以看到我使用的

是Windows Vista或Win7操作系统,使用Windows Server 2008 Web编辑器来部

署DataSnap ISAPI服务.

2. DATASNAP目标:如何获取数据

DataSnap2010支持三种不同的Windows方式:VCL窗体,Windows服务和控制台应

用程序.本节中我们

将讨论他们的好处,不同和每种方式最适合在什么情况下使用.

下面会创建一个DataSnap服务端和客户端,我们将讲解

TDSServer,TDSServerClass,TDSTCPServerTransport,TDSHTTPService,TDSHTTP

WebDispatcher和

TDSHTTPServiceAuthenticationManager组件,以及自定义的服务方法和

TDSServerModule类.

将讨论不同的传输协议(TCP,HTTP)的好处及传输效率.并讨论DataSnap服务对

象的不同生命期选项

(Server,Session,Invocation),及他们的效率和使用的建议.最后,讨论部署.

2.1. DATASNAP 服务端范例

在Object Repository中有两个不同的DataSnap服务向导:一个是生成基于

Windows的Datasnap服务

项目,一个是生成基于WebBroker的DataSnap服务项目(需要部署到IIS或

Apache).我们将会演示.

启动了Delphi2010,点击,你会在Object Repository中看到

DataSnap服务向导中

显示的三个图标:DataSnap Server,DataSnap WebBroker Server,和Server

Module.

双击第一个(后面的两个在下面的小结中讲解),弹出如下对话框:

界面中第一部分是控制项目类型的.默认可以生成可视化的带有主窗体的VCL窗

体应用程序.第二个

选项是创建控制台应用程序,生成一个控制台窗口—可以用来输出请求应答信息

(用Writeln语句输出

服务应用程序正在做什么).这两种方式都是为了做范例或最初部署,很少用于最

终部署.由于

DataSnap架构不再基于COM,客户端将不能使服务端启动.因此为了响应客户端

的请求,DataSnap服务

端应该一直在运行.如果你希望应用7X24小时全天候运行,DataSnap服务端必须

同时也在运行中.对应

VCL窗体或控制台应用程序,需要一个账户登录到Windows中后才能启动

DataSnap服务,背离了这种要

求.第三者选择在这时最适合:一个Windows服务应用程序,安装后配置成为自动

启动,当计算机启动后

将自动运行(不需要账户登录).服务应用程序不会弹出界面,很难调试Bug.然

而,为了整合这三种的优

势,我将用几分钟创建一个项目组,包括VCL窗体应用程序的DataSnap服务,控制

台DataSnap服务,及

Windows服务Datasnap服务,都共享同一个自定义的服务方法,这样就可以开发

一个Datasnap服务应用

程序,在需要的时候编译出三个不同类型的部署方式.

第二部分是选择使用的Datasnap服务的通讯协议.和DanaSnap2009相比,我们可

以看到多了一个

HTTP通讯,及HTTP验证.为了更加灵活,这里建议选择全部选项,我们可以同时使

用TCP/IP,HTTP,及使

用HTTP引入的HTTP验证.

第三部分已经为我们配置好了,如果我们要提供一个服务方法类,我们可以选择

它的基

类:TPersistent,TDataModule或TDSServerModule.推荐使用最后的一个选项,

可使用RTTI来启动执行

函数 (也可能你觉得使用TDataModule更合适—不操作数据库,或不使用其他非

可视控件,这时使用

TPersitent也够用了).

现在是从中贴出来的一小段代码,来说明TDSServerModule和

TProviderDataModule(也是继承于TDataModule)之间的关系.

TDSServerModuleBase = class(TProviderDataModule)

public

procedure BeforeDestruction; override;

destructor Destroy; override;

end;

{$MethodInfo ON}

TDSServerModule = class(TDSServerModuleBase)

end;

{$MethodInfo OFF}

当无法确定时就使用TDSServerModule选项作为基类.

2.1.1. 创建多目标项目组-- VCL 窗体项目

如上面所说,这里创建多目标的Datasnap服务项目组.首先创建一个VCL窗体应

用程序作为Datasnap

服务,选择所有的通讯协议.

默认创建了一个叫做的项目,并带有三个单元文

件,,和.首先

Project As保

存项目,并输入有实际意义的文件名称.将保存为

,

保存为文件,保存为

.

稍后我们将向项目组添加控制台应用程序和Window服务应用程序.首先我们来

检查一下项目,并编

译工程.如果你编译DataSnapServer项目,将会出现一个错误信息(由于我们将

改名所致).错误原因是由于

单元中的

Implementation部分引用了单元.为了修复这个冲突,

修改引用单元的文件

名称,从新编译.这是发现在第37行出现错误,使用了ServerMethodsUnit1中的

TServerMethods1类型.

修改ServerMethodsUnit1为ServerMethodsUnitDemo.这时可以正确的编译项目

了.

ServerContainerUnitDemo的引用部分应该向下面代码所示:

implementation

uses

Windows, ServerMethodsUnitDemo;

{$R *.dfm}

procedure erClass1GetClass(

DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);

begin

PersistentClass := rMethods1;

end;

end.

2.1.1.1. SERVERCONTAINERUNITDEMO

打开ServerContainerUnitDemo单元,将会看到不少于五个组件:一个

TDSServer,一个

TDSServerClass,一个TDSTCPServerTransport(用于TCP/IP通讯),一个

TDSHTTPService(用于HTTP

通讯),一个TDSHTTPServiceAuthenticationManager组件(用于HTTP验证).

前面两个一直会存在,其他的三个则是根据选择的通讯协议生成的.

2.1.1.1.1. TDSSERVER

TDSServer组件只有四个属性,AutoStart,HideDSAdmin,Name和art

属性默认设置为

True,意味着在窗体创建后自动启动DataSnap服务.如果将AutoStart设置为

False,需要手动调用

Start方法启动服务,并调用Stop方法停止服务.可以调用Started方法验证

DataSnap服务是否已经

启动.

HideAdmin属性默认设置为False.如果设置为True,连接到DataSnap服务的客

户端将无法调用

Datasnap服务中的TDSAdmin类的内置方法.TDSAdmin不是一个真正的类,我们可

以调用的TDSAdmin

方法定义在DSNames单元:

TDSAdminMethods = class

public

const CreateServerClasses = 'ServerClasses';

const CreateServerMethods = 'ServerMethods';

const FindClasses = 'asses';

const FindMethods = 'thods';

const FindPackages = 'ckages';

const GetPlatformName = 'tformName';

const GetServerClasses = 'verClasses';

const GetServerMethods = 'verMethods';

const GetServerMethodParameters = 'verMethodParameters';

const DropServerClasses = 'rverClasses';

const DropServerMethods = 'rverMethods';

const GetDatabaseConnectionProperties =

'abaseConnectionProperties';

end;

TDSServer组件有五个事件:OnConnect,OnDisconnect,OnError,OnPrepare和

OnTrace.我们可以

实现这五个事件来响应不同的情况,例如向日志文件中写入日志.

OnConnect,OnDisconnect,OnError和OnPrepare事件有一个继承于

TDSEventObject的参数,包含

了DxContext,传输,服务和DbxConnection组件的属性,在OnConnect和

OnDisconnect事件中

TDSConnectEventObject类型还包含了ConnectionProperties和ChannelInfo

属性.

TDSConnectEventObject也包括了由错误引起的异常, TDSConnectEventObject

还包括了我们要使

用的MethodAlias和ServerClass属性.

OnTrace事件有一个TDBXTraceInfo类型的参数.注意由于这个OnTrace事件处

理程序也会包含一

些代码错误,如TDBXTraceInfo和CBRType是编译器未知的.为了解决这个问题,

我们需要引用

DBXCommon单元(为识别TDBXTraceInfo类型)和DBComonTypes单元(为识别

CBRType类型).

在OnConnect事件处理中,我们可以通过ChannelInfo来查看连接信息,例如(使

用自定义的函数

LogInfo向日志文件中写入信息):

procedure er1Connect(

DSConnectEventObject: TDSConnectEventObject);

begin

LogInfo('Connect ' + );

end;

在OnTrace事件处理程序中我们可以使用e中的信息记录服务

端正在做什么.

function er1Trace(TraceInfo: TDBXTraceInfo):

CBRType;

begin

LogInfo('Trace ' + Category);

LogInfo(' ' + e);

Result := cbrUSEDEF; // take default action

end;

注意,在客户端也可以使用连接到TSQLConnection组件的TSQLMonitor组件来跟

踪DataSnap服务

端和客户端之间的通讯(在创建这个DataSnap服务的客户端时讲解).

一个跟踪日志输出如下所示:

17:05:55.492 Trace

17:05:55.496 read 136 bytes:{"method":"reader_close","params":[1,0]}

{"method":"prepare","params":[-1,false,"Method",

"_GetRecords"]}

17:05:55.499 Prepare

如你所见,e中包括了传输信息的字节数和被调用的方法名称

等信息.

2.1.1.1.2. TDSSERVERCLASS

TDSServerClass组件将服务端特定的类发布给远程客户端(使用动态方法调

用).

TDSServerClass组件有一个Server属性指向TDSServer组件.其他除了Name和

Tag外的重要属

性是LifeCycle.默认是Session,但是也可设置为Server或Invocation.从长到

短,Server,Session

和Invocation的意思是一个类的实例在服务端的生命周期为整个服务,一个

DataSnap会话或一次

方法调用.Session表示每个连接将获取其自己的服务类实例.如果将其改为

Invocation,将会得到

一个无状态的服务类—可用于部署CGI Web服务应用程序(其也是无状态的,每个

请求都进行加载

卸载).将LifeCycle改为Server,则所有的连接请求使用一个服务类实例.这可

以用于计算请求数

量,但是必须自己保证线程安全.

TDSServerClass有四个事件:OnCreateInstance,OnDestroyInstance(当实例创

建和注销时触

发).OnGetClass和are事件可用于准备服务方法.使用D2009

或使用D2010手动向

容器中添加TDSServerClass时,OnGetClass事件必须由我们自己实现,以便于指

定一个可远程调用

的类.在D2010的向导中,已经自动为我们实现了OnGetClass事件,如下:

procedure erClass1GetClass(

DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);

begin

PersistentClass := rMethods1;

end;

注意:当我们重命名了自动生成的代码单元ServerMethodsUnit1为

后必须修改这里.

2.1.1.1.3. TDSTCPSERVERTRANSPORT

TDSTCPServerTransport组件负责在DataSnap服务端和客户端进行通讯,使用

TCP/IP协议.

TDSTCPServerTransport组件有五个重要的属性:BufferKBSize,Filters(D2010

新特

性),MaxThreads,PoolSize,Port和Server.

BufferKBSize属性指定通讯缓冲区大小,默认设置为s属性可以包

含一个传输过滤

器集合,将在第四节讲解.MaxThreads属性定义最大线程数(默认为0不限

制).PoolSize可用于连接

池(如果修改了这里,也需要相应的修改DataSnap客户端).

Server属性指向TDSServer组件.TDSTCPServerTransport组件没有事件.

2.1.1.1.4. TDSHTTPSERVICE

TDSHTTPService组件负责使用HTTP协议组织DataSnap服务端和客户端通讯.

TDSHTTPService组件有十个属性(除了Name和

Tag):Active,AuthenticationManager,DSHostName,DSPort,Filters,HttpPort,

Name,RESTContext,Server,和只读的ServerSoftware属性.

Active属性指定DSHTTPService开始侦听请求.可以在设计时设置,但是这会影

响DataSnap服务

在运行时启动(由于DSHTTPService组件侦听了同一个端口—在设计时启动侦

听,在运行时就不能

再启动一个侦听了).最好的方式在在TServerContainer的OnCreate事件中激活

TDSHTTPService:

procedure duleCreate(Sender: TObject);

begin

:= True;

end;

AuthenticationManager属性用于定义处理HTTP验证的管理组件,这里指向了

TDSHTTPServiceAuthenticationManager组件.这个组件在下节详述.

DSHostName和DSPort属性用于定义DataSnap服务端连接,但只有在没有指定

Server属性时生效.

通常都是使用Server属性.

Filters属性可以包含一系列传输过滤器,在第四节详述.

HttpPort属性定义DSHTTPService组件侦听的特定端口以响应连接.注意这个属

性默认是80端口,

通常在发布时必须做修改(IIS等Web服务已占用了80端口).

RESTContext属性指定REST上下文URL,这样就可以以REST服务的方式调用

DataSnap服务.默

认,RESTContext属性设置为rest,我们可以用

localhost/datasnap/rest/...来调用服务.

在第六节详述REST,JSON和客户端回调函数.

最后,Server属性指向同一个容器中的TDSServer组件.如果没有指定Server属

性,也可以使用

DSHostName和DSPort属性连接到使用TCP的DataSnap服务.当设置了Server

属性,DSHostName和

DSport属性失效.

TDSHTTPService组件有五个事件:四个是REST相关的,一个是跟踪事件,REST相

关事件将在第六

节详述.

OnTrace事件可用于跟踪对DSHTTPService组件的调用,例如:

procedure Service1Trace(Sender: TObject;

AContext: TDSHTTPContext; ARequest: TDSHTTPRequest;

AResponse: TDSHTTPResponse);

begin

LogInfo('HTTP Trace ' + ng);

LogInfo(' ' + nt);

LogInfo(' ' + seText);

end;

注意HTTP跟踪信息只有当客户端使用HTTP连接到服务端是才会触发(默认使用

TCP/IP协议).

跟踪输出如下所示:

17:05:55.398 HTTP Trace TDSHTTPContextIndy

17:05:55.400 /datasnap/tunnel

17:05:55.403 OK

从中可见,AContext设置为TDSHTTPContextIndy,ARequest设置为

/DataSnap/tunnel,AResponse

设置为OK.

2.1.1.1.5. TDSHTTPSERVICEAUTHENTICATIONMANAGER

当选中HTTP通讯协议的验证复选框后,TDSHTTPServiceAuthenticationManager

组件将自动出现

在服务容器中.也可手动添加到服务容器中,当然TDSHTTPService组件的

AuthenticationManager

属性必须指向TDSHTTPServiceAuthenticationManager组件.

TDSHTTPServiceAuthenticationManager组件有一个事件:OnHTTPAuthenticate

事件,可以验证

Datasnap客户端到服务端连接的HTTP信息.

procedure

ServiceAuthenticationManager1HTTPAuthenticate

(

Sender: TObject; const Protocol, Context, User, Password: string;

var valid: Boolean);

begin

if (User = 'Bob') and (Password = 'Swart') then

valid := True

else

valid := False

end;

当然,你可以使用数据库技术来扩展验证方式.客户端最好使用HTTPS方式将用

户名和密码等

HTTP验证信息发送到服务端,所以我希望易博龙可以在现有HTTP和TCP/IP基础

上在添加一个HTTPS

协议.

HTTPS可以确保连接安全和数据包加密, 数据包被窃取也不会泄露用户和密码

信息.可与你所在

域的ISP或Web管理员协商是否有可能使用HTTPS—--强烈推荐(如我使用的

是).

DataSnap服务应用程序另一个优势是可将HTTP验证信息(所用协议和上下文信

息)记录下来.以

便于查找谁登陆过,谁试图登录(如做欺骗登录操作试图获取DataSnap服务的权

限).

2.1.1.2. SERVERMETHODSUNITDEMO

注意我们已经检查了单元,现在看一下DataSnap

服务的另外重要

的单元:单元.在新建DataSnap服务对话框中我们

指定了

TDSServerModule类作为基类,因此TServerMethods1类型继承于

TDSServerModule(其继承于

TDSServerModuleBase,又继承于TProvideDataModule,添加一个析构函数和

BeforeDestruction过

程. TProvideDataModule继承于正常的TDataModule,增加了服务提供者的能力

更多信息见后

面).

由于TServerMethods1继承于TDataModule,设计时可以看到一个数据模块的可

视区域.我们可以

向其中添加一下不可视组件,如数据存取控件等.在第三节将操作数据库,现在保

持设计区为空,仅

向TServerMethods1类添加方法.

如果不想向项目组添加其他类型的项目—DataSnap控制台应用程序及DataSnap

Windows服务应

用程序,可跳到2.1.4节查看实现服务方法.

2.2. DATASNAP 客户端

第一个DataSnap服务端范例启动并侦听请求,现在创建一个客户端.本节中,我

们将讲解如何在

客户端连接服务端,如何导入方法生产服务类.

确信DataSnap服务已启动,我们创建一个DataSnap客户端应用程序项目.为了在

设计时方便切换

DataSnap服务项目和DataSnap客户端项目,将客户端项目添加到同一个项目组.

任何类型的项目都

可作为DataSnap客户端项目,这里我们选择VCL窗口应用程序,保存为

,主窗体

为.控件栏中DataSnap服务目录中含有六个DataSnap(服务)组

件,DataSnap客户端

目录中不含我们现在需要的组件.

DataSnap客户端目录中的组件都是一些老的DataSnap组件,可以使用,但不推

荐.但可使用新

的TDSProviderConnect组件,可以在新的DataSnap客户端上连接老的DataSnap

服务(3.2中详述).

除了DataSnap客户端目录,我们还要看看dbEpress目录,可以找到一个新的组件

叫做

TSQLServerMethod(注意:在下一个截图中这个新组件很容易发现,其用TSql前

缀替代了TSQL前

缀).

TSqlServerMethod组件可用于调用DataSnap服务的远程方法,但首先需要连接

到DataSnap服务.

可以使用TSQLConnect组件建立连接—--不在使用原来的TXXXConnection组件.

为了灵活

性,TSQLConnection组件的Drive属性下拉框中有个新选项:DataSnap.在

ClientForm上放置一个

TSQLConnection组件,设置其Driver属性为属性将变成可展

开的对象,展开后如

下图:

注意:默认CommunicationProtocol属性为空,TSQLConnection将使用TCP/IP作

为通信协议(端口

211).BufferKBSize为32(KB),端口设置为211(与服务端设置相同).实际应用中

一般将端口号设置

为其他端口(同时修改服务端客户端),因为211端口是DataSnap默认端口不安

全.

HostName,UserName和Password用于连接到DataSnap服务.在本地测试时,将

HostName设置为

localhost,通常可以设置为服务器名称,DNS或IP地址.

不要忘记将TSQLConnection的LoginPropt属性设置为False,否则将会在连接

的时候弹出登录窗

口.

一旦TSQLConnection的Driver属性设置好,就可以设置Connected属性为True

去连接DataSnap服

务端.注意这时DataSnap服务必须运行才能连接成功.

2.2.1. DATASNAP客户端类

在确定连接正常后,右击TSQLConnection组件,选择Generate DataSnap Class

选项,将生成一个

新单元,默认叫做Unit1,含有一个叫做TServerMethods1Client的类(在

DataSnap服务方法类名称

后加了一个Client).保存单元为.

这个TServerMethods1Client类如下所示

type

TServerMethods1Client = class

private

FDBXConnection: TDBXConnection;

FInstanceOwner: Boolean;

FEchoStringCommand: TDBXCommand;

FServerTimeCommand: TDBXCommand;

public

constructor Create(ADBXConnection: TDBXConnection); overload;

constructor Create(ADBXConnection: TDBXConnection;

AInstanceOwner: Boolean); overload;

destructor Destroy; override;

function EchoString(Value: string): string;

function ServerTime: TDateTime;

end;

如你所见,TServerMethods1Client类有两个构造方法,一个析构方法和两个我

们在服务端定义

的服务方法.

为使用这些方法,将ServerMethodsClient单元引用到ClientForm,在客户端窗

体上放置一个按

钮,在按钮的OnClick事件中写如下代码:

procedure 1Click(Sender: TObject);

var

Server: TServerMethods1Client;

begin

Server := (nection);

try

ShowMessage(DateTimeToStr(Time))

finally

end;

end;

这个代码将创建一个TServerMethods1Client类实例,然后调用ServerTime服务

类,最后释放这

个DataSnap服务的代理对象.

点击按钮将弹出对话框显示服务端的时间.

同样方法测试EchoString.

2.2.1.1. HTTP COMMUNICATION PROTOCOL

注意我已经提到TSQLConnection组件以TCP/IP作为默认通讯协议.这样我们就

看不到任何HTTP

跟踪信息(在2.1.1.1.4小结中定义).然而,很容易修改通讯协议,仅需在

TSQLConnection的Driver

属性中的CommunicationProtocal子属性中输入HTTP即可.注意这是我们还要修

改Port属性,因为

211被TCP/IP占用,同时要修改服务端的TDSHTTPService组件的端口号.

修改后运行DataSnap客户端,会出现如下错误:

解决方法是在DataSnap客户端向ClientForm中添加DSHTTPLayer单元引用.

2.2.1.2. HTTP 验证

使用HTTP通讯协议的好处之一是可以使用其包含的HTTP验证.由DataSnap服务

端的

TDSHTTPServiceAuthenticationManager组件所支持(详见2.1.1.1.5).

如果实现了OnHTTPAuthenticate事件处理,将会核对HTTP验证.我们必须保证输

入正确的信息才

能确保TDSHTTPServiceAuthenticationManager验证通过.否则将会得到一个

HTTP/1.1 401未验证

错误.

HTTP检验从客户端传递到服务端的用户名和密码,及其他

TDSHTTPServiceAuthentication特定

信息,我们需要在DataSnap客户端的TSQLConnection控件中填写DSAuthUser

和DSAuthPassword属

性.

注意我们也需要指定HostName的值,除非是在同一台电脑上测试.

2.3. DATASNAP服务部署

范例的服务端和客户端在同一台电脑上运行良好,但是实际环境中,DataSnap服

务将运行在服务

器上,一或多台客户端通过网络连接服务端.服务端程序通常部署在没有安装

Delphi的电脑上.这

种情况下就需要考虑不用运行时包来编译DataSnap,而仅生成一个大的可执行

文件.由于我们还没

有使用任何数据操作组件,也不需要任何的其他数据库驱动或DLL文件.

2.3.1. DATASNAP 客户端部署

假设客户端与服务端运行在不同的电脑上,我们必须保证客户端可以连接到服务

端.为了保证这

点,客户端的TSQLConnection组件不能仅仅指定Driver属性中的

CommunicationProtocol和端口,

还需要指定HostName属性.设置IP地址或DNS.例如连接我的DataSnap服务的

HostName属性

为.(注意不需要指定前缀,因为在

CommunicationProtocol属性中指定了

通讯协议).

3. DATASNAP和数据库

除了使用Delphi2010 DataSnap框架创建简单的服务方法,我们还可以在服务端

添加数据库操作,

实现多层数据库应用—DataSnap服务连接到数据库,但DataSnap客户端是瘦客

户端,不含任何数据

库驱动.

我们同样可以快速创建数据库操作的DataSnap范例,只使用SQLConnection组件

和已创建的客户

端类.也可使用另外两个DataSnap组件TsqlServerMethod和

TDSProviderConnection.

首先,我们要确保服务端公布了一个数据集,打开ServerMethodsUnitDemo并在

数据模块中添加

一个TSQLConnection组件.连接TSQLConnection组件到数据库和表(本例我连接

到BlackFishSQL的

Employees表).将TSQLConnection组件添加到数据模块的设计区域.设置Driver

属性为

BlachFishSql.然后设置Database属性为文件的路径:

C:Documents and SettingsAll

UsersDocumentsRAD Studio7.0DemosdatabasedatabasesBlackfishSQL

on Windows

XP, or C:UsersPublicDocuments RAD

Studio7.0DemosdatabasedatabasesBlackfishSQL.

将TSQLConnection的LoginPrompt属性设置为False.设置Connected属性为

True验证是否能正确连

接.

下一步,添加一个TSQLDataSet组件,将其SQLConnection设置为

TSQLConnection组件.

设置其CommandType为ctQuery,双击CommandText属性输入SQL语句.

SELECT EMP_NO, FIRST_NAME, LAST_NAME, HIRE_DATE, JOB_COUNTRY FROM

EMPLOYEE

确保在设计时设置了TSQLConnection组件的LoginPrompt和Connected属性为

False,及

TSQLDataSet的Active属性为False.

现在在ServerMethodsUnitDemo单元的TServerMethods1类中添加一个public

方法返回一个

TSQLDataSet组件.

type

TServerMethods1 = class(TDSServerModule)

SQLConnection1: TSQLConnection;

SQLDataSet1: TSQLDataSet;

private

{ Private declarations }

public

{ Public declarations }

function EchoString(Value: string): string;

function ServerTime: TDateTime;

function GetEmployees: TDataSet;

end;

如你所见,TServerMethods1类中定义了一个叫做GetEmployees的方法,实现如

下:

function loyees: TDataSet;

begin

; // make sure data can be retrieved

Result := SQLDataSet1

end;

重新编译服务并运行.注意如你关闭了服务但无法重新编译,可能是DataSnap服

务还在运行.

3.1. TSQLSERVERMETHOD

回到DataSnap客户端,TSQLConnection组件应该已经断开到DataSnap服务端的

连接(如果仍然连

接,说明你没有重新编译服务端).重新将Connected设置为True,从服务端更新

信息,并重新从服务

端生成客户端类(使用右键菜单).

为了重写原来的ServerMethodsClient单元,推荐从项目中移除原来的

ServerMethodsClient后

在选择’Generate DataSnap Client Classes’方法.保存了新的单元文件后不

需要修改客户端代

码.生成了新文件后,可以从中看到GetEmplorees方法.

type

TServerMethods1Client = class

private

FDBXConnection: TDBXConnection;

FInstanceOwner: Boolean;

FEchoStringCommand: TDBXCommand;

FServerTimeCommand: TDBXCommand;

FGetEmployeesCommand: TDBXCommand;

public

constructor Create(ADBXConnection: TDBXConnection); overload;

constructor Create(ADBXConnection: TDBXConnection;

AInstanceOwner: Boolean); overload;

destructor Destroy; override;

function EchoString(Value: string): string;

function ServerTime: TDateTime;

function GetEmployees: TDataSet;

end;

为使用GetEmployees方法获取TDataSet,我们可以使用TsqlServerMethod组

件.将

TsqlServerMethod组件添加到客户端窗体,设置其SQLConnection属性为

SQLConnection1,然后打

开ServerMethodName下拉框显示可用的方法:一些DSAdmin方法(可通过设置

TDSServer的

HideDSAdmin属性为True禁止),接下来是三个DSMetaData方法,七个

_XXX方法

(由原来的IAppServer提供),和最后我们自己的TServerMethods1的方

法:EchoString,ServerTime,

和GetEmployees方法.

本例我们选择loyees作为ServerMethodName属性的

值.根据

ServerMethodName属性值,SqlServerMethod控件获取查询结果记录集.

我们使用TDataSetProvider,TClientDataSet和TDataSource来使数据显示在

TDBGrid中.

在客户端添加一个TDataSetProvider控件,将其DataSet设置为

SqlServerMethod控件.下一步添

加一个TClientDataSet控件,将其ProviderName设置为DataSetProvider控件.

设置其

RemoteServer属性为空—--这是用于原有DataSnap的方法,在新的DataSnap架

构中不再使用.

最后,放一个TDataSource控件,设置其DataSet为TClientDataSet控件,然后就

可以放TGBGrid和

TDBNavigator控件了.设置其DataSource为TDataScource组件,将可以在客户

端窗体中看到数据.

为了在设计时验证连接,我们设置ClientDataSet的Active属性为True.将触发

SqlServerMethod

的Active为True(开始检索数据,然后自动将Active设置为False),同时会将

ted设置为True连接到服务端.

这种方式连接一直保持,TCliendtDataSet和TSQLConnection的被激活,数据显

示在TDBGrid中.

这种方式很容易以只读方式查看数据.注意这里说只读,因为TSQLServerMethod

不允许

TDataSetProvide-TClientDataSet组合将客户端的数据变更传回服务端.

TSQLServerMethod是轻量级方式连接获取只读数据.这样,如果你需要获取

DataSnap服务器上的

数据但不需要做修改,当前这种发布一个返回TSQLDataSet结果集方法的架构就

很好.

然而,有时我们需要更新数据,这时就必须使用不同的方法获取数据.

3.2. TDSPROVIDERCONNECTION

如果我们想提交更新,我们就需要TDSProviderConnection组件与服务端的

TDataSetProvider关

联,因此我们不但可以读取数据也可以修改数据.

首先,我们需要修改服务端数据模块,现有的只是返回一个TDataSet,我们必须

添加一个实际的

TDataSetProvider,确保将其从DataSnap服务端发布到客户端.所以,回到

ServerMethodsUnitDemo

单元放一个TDataSetProvider组件,设置其DataSet属性为TSQLDataSet.应该

将TDataSetProvider

重命名,如dspEmplyees;

现在,重新编译DataSnap服务,运行.然后修改客户端.

3.2.1. TDSPROVIDERCONNECTION 客户端

为检索到发布的TDataSetProvider组件需要修改一下DataSnap客户端.将

TSQLServerMethod和

TDataSetProvider组件从客户端窗体删除,添加一个TDSProviderConnection组

件.设置其

SQLConnection属性为TSQLConnection组件,连接到DataSnap服务.我们也需要

设置一个

ServerClassname属性值(很不幸不能选择只能输入).现在只能手动输入

TDSServerModule的名字,

这里是TServerMethods1.

在前一个例子中,TClientDataSet只设置了一个ProviderName属性.然而,使用

TDSProviderConnection组件,我们必须首先设置其RemoteServer属性为

TDSproviderConnection

组件,然后设置ProviderName属性(这个属性还为原来设置的

DataSetProvider1,现在设置为

dsEmployeer----在服务端发布的TDataSetProvider组件的名称).

在ProvideName属性的下拉框中显示dspEmployees选项(在服务端的

ServerDataModule单元中发

布的名称).

现在我们可以设置为True,在设计时查看数据.

设置为True,同时将ted置为

True.注意在设计

时最好不要将这两个属性设置为True.首先,如果你在IDE中打开DataSnap客户

端项目,将会试图连

接服务端,如果服务端没开启将失败.其次,如果在运行时启动应用程序,并且连

接不可用,应用程

序将会抛出异常.从而应用程序不能使用本地数据,导致远程连接无效将不能使

用应用程序.

最好的方式是使用菜单选项或按钮明确的设置TSQLConnection组件连

接,TClientDataSet组件

进行获取数据.并提交包括username/password的信息,将在后面说明.现在确保

属性设置为False,同时ted属

性为False.在客户

端窗体中放一个按钮,在OnClick事件中明确的打开TClientDataSet.

procedure 2Click(Sender: TObject);

begin

;

end;

现在添加代码提交数据变更,保存回服务器.

3.2.2 数据库更新

有两种方法将数据修改保存会服务端:自动和手动.都是调用一下方法,但是会自

动调用或手动

调用,各有优缺点.

对于自动方式,我们可以使用TClientDataSet的数据修改时触发的

OnAfterInsert,OnAfterPost

和OnAfterDelete事件.在事件处理程序中,实现很简单,调用TClientDataSet

的ApplyUpdates方法,

发送变更,将Delta包发送到服务端保存回数据库.

procedure DataSet1AfterPost(DataSet: TDataSet);

begin

pdates(0);

end;

如果发生了更新错误,将会触发TClientDataSet的OnReconcileError事件,更多

信息见3.2.3

手动方式发生更新也是使用TClientDataSet的ApplyUpdates方法.但是这时方

法不在

OnAfterInsert,OnAfterPost和OnAfterDelete事件中执行.而是我们添加一个

按钮让用户显示的提交更

新.

procedure ateClick(Sender: TObject);

begin

pdates(0);

end;

自动提交的好处当然用户不会忘记将变更保存回服务端.然而,缺点是无法提供

Undo能力.一旦

提交数据就更新回了服务器.另外,如果使用手动提交,所有变更保存在客户端

---TClientDataSet

组件的内存中.这样就允许用户Undo部分变更:使特定记录或全部记录放弃更

新.点击更新按钮显

示调用ApplyUpdate方法.可能会导致用户忘记提交修改数据.我们应该在窗口

关闭时添加代码检

查TClientDataSet中是否还有未提交数据(检查Count

属性).

3.2.3. RECONCILE ERRORS

pdates方法有一个参数:应用更新时允许发生的最大错

误数量.如果有

两个客户端连接到了DataSnap服务端,获取Employees数据并同时修改了第一行

数据.依据目前为

止我们的实现,两个客户端都会使用TClientDataSet的ApplyUpdates将数据变

更到DataSnap服务

端.如果都将ApplyUpdates的参数MaxErrors设置为0,则第二个客户端的提交

将会停止.第二个客

户端应该使用一个大于0的参数指定允许的错误/冲突数.然而,即使第二个客户

端将MaxErrors设

置为-1(不管有多少错误发生都继续提交后面的更新记录),都不会提交被第一个

用户更新过的记

录.换句话说,你需要执行一系列冲突处理来解决这些更新已经被更新的记录或

列的冲突问题.

幸运的是,Delphi提供了一个很强大的对话框来处理这个问题.当在DataSnap客

户端需要做一些

冲突处理时,都可以使用这个对话框(或自己实现,但最终都是处理冲突问题).

使用Delphi提供的功能,,在Delphi文件子目录中选择

Reconcile Error对话框

图标.

选中这个图标点击OK,保存为,加入到DataSnapClient项目.这个

单元包括了定义

和实现更新错误对话框.来解决数据库更新错误.

ReconcileErrorForm窗体实例将按需要动态创建.那么如何使用这个特殊的

ReconcileErrorForm窗体呢?

好,其实很简单.对于每个没有成功更新的记录,都会触发TClientDataSet的

OnReconcileError事件.定义如

下:

procedure DataSet1ReconcileError(DataSet: TClientDataSet;

E: EReconcileError; UpdateKind: TUpdateKind;

var Action: TReconcileAction);

这个事件处理程序与四个参数,第一个是抛出错误的TClientDataSet,第二个参

数是引发错误冲

突的原因,第三个参数是更新类型UpdateKind(insert,delete,modify),第四个

参数是你要如何处

理冲突.可以返回如下枚举类型值:

- raSkip:不更新这条记录,但在变更日志中保留未提交的变更,下次提交在试.

-raAbort:取消记录冲突处理..

-raMerge:将更新记录与远程数据库记录合并,仅在客户端变更修改过的远程字

-raCorrect:使用正确的值替换更新记录,这需要用户介入.

-raCancel:对本记录的修改全部放弃.回到初始值状态.

-raRefresh:对本记录修改全部放弃,但重新加载当前数据库的记录值.

关于ReconcileErrorForm不需要考虑全部执行选项.只需要做两件事件.一,在

DataSnap客户端主窗体中

引用错误处理对话框单元.二,在OnReconcileError事件中写一行代码调用

ReconcileErrorForm单元中的

HandleReconcileError全局函数. HandleReconcileError函数也有四个同样的

参数,只需要按顺序传递即可.

如下所示:

procedure DataSet1ReconcileError(DataSet:

TClientDataSet;

E: EReconcileError; UpdateKind: TUpdateKind;

var Action: TReconcileAction);

begin

Action := HandleReconcileError(DataSet, UpdateKind, E)

end;

3.2.4. 示范冲突错误

现在最大的问题是:实际工作中如何使用的冲突处理?为了测试,需要两个或更多

DataSnap客户

端同时运行.为使用当前的客户端和服务端进行测试,需要执行如下步骤:

-启动服务端应用程序

-启动第一个客户端应用程序,点击链接按钮,获取数据

-启动第二个客户端应用程序,点击链接按钮,获取数据

-使用第一个客户端应用程序,修改第一行数据的FirstName列

-使用第二个客户端应用程序,修改第一行数据的FirstName列

-在第一个客户端应用程序中点击更新按钮

-在第二个客户端应用程序中点击更新按钮,这时将会发生一个或多个错误.因为

第一个应用程

序已经修改了同一行的同一个列.引起冲突. OnReconcileError被触发.

-进入更新错误对话框,现在可以处理冲突(忽略(Abort),取消(Abort),合并

(Merge),更正(Correct),取消

(Cancel),更新(Refresh)).测试一下Skip和Cancel的不同,及

Correct,Refresh和Merge的不同.

Skip移动到下一行记录,忽略更新请求.但其更新将保留在更新日志中.Cancel

也忽略更新请求

同时清除本记录所有以前的更新记录.

Refresh清除本记录所有的变更记录,并将数据库中的值作为当前记录的

值.Merge试图将数据库

记录和更新记录合并.将变更提交到数据库.更新和合并的记录都不会再进行处

理,记录已经与数

据库同步.

Correct是一个强大的选项,在事件处理中给你一个指定更新记录值的机会.需

要写代码或弹出

对话框指定新值.

3.3. DATASNAP 数据库部署

部署一个使用数据库的DataSnap服务需要比部署一个简单DataSnap服务的步骤

要多些.客户端,

没什么变化,还是一个瘦客户端,如将MidasLib加入到了项目引用就仅需部署一

个单一的可执行文

件.

服务端,必须部署数据库驱动.及所选数据库依赖的驱动和文件.使用DBX4,确保

发布

TSQLConnection组件和及文件(可在

C:Documents and

SettingsAll Users DocumentsRAD

StudiodbExpress7.0 directory on Windows XP or in the

C:UsersPublicDocumentsRAD

StudiodbExpress7.0中找到). 文件指定所用驱动,

DriverPackageLoader及

MetaDataPackageLoader(通常指向同一个包).对于BlackFishSQL,使用的

文件,需要

部署到服务端.更多关于部署BlashFishSQL信息见RAD Studio7.0 目录下的

deploy_文件.

3.4. 重用已有的远程数据模块

如果你有一个远程数据模块类,也可以将其组合到新的DataSnap项目中来.但是

必须要牺牲一些特性,尤其

是引入了COM.

首先,如果有一个你要迁移的DataSnap服务应用程序,而不仅仅是一个远程数据

模块,你需要使用命令行

/unregister注销DataSnap服务.不做这步将无法注销远程数据模块.在远程数

据模块单元,移除initialization区

域.如果还希望这个单元在D2007及一下版本重用,可以使用编译开关:

{$IF CompilerVersion >= 20}

initialization

(ComServer, TRemoteDataModule2010,

Class_RemoteDataModule2010, ciMultiInstance, tmApartment);

{$IFEND}

end.

从项目中移除UpdateRegistry函数或用编译开关修饰:

{$IF CompilerVersion >= 20}

class procedure UpdateRegistry(Register: Boolean;

const ClassID, ProgID: string); override;

{$IFEND}

最重要的变更—将项目转换为无COM依赖的DataSnap服务.-及移除类型库

(.ridl文件)和类型库导入单元.这

无法通过编译器开关修改,因此需要为D2007及一下版本和D2009及以上版本分

别生成一个项目文件.在

TRemoteDataModule类中放一个TDSServerClass组件.最后,将所有的自定义方

法都转到

TRemoteDataModule单元的public节中.

4. DATASNAP 过滤器[FILTER]用法

本节将说明过滤器工作原理,及如何使用已存在的过滤器(如压缩)或创建新的

DataSnap过滤器.DataSnap

过滤器是一个特殊的DLL,拦截通讯流,在整个过滤器链中操作通讯流.所以本例

中我们可以使用压缩和解压缩

过滤器,或记录压缩等.

必须在客户端和服务端指定过滤器.在服务端,必须指定

TDSTCPServerTransport组件的过滤器属性列表.

在客户端,必须在客户端项目中引用过滤器单元文件.对于客户端这就足够了,因

为每个DataSnap过滤器都会

自动注册.

当处理OnConnect事件时,可以检查用于连接的已注册的过滤器,例如使用自定

义的日志函数输入日志信息,

procedure er1Connect(

DSConnectEventObject: TDSConnectEventObject);

var

i: Integer;

begin

LogInfo('Connect ' + );

for i:=0 to -1 do

LogInfo(' Filter: ' +

ter(i).Id);

end;

4.1. ZLIBCOMPRESSION FILTER

作为范例,我们使用已随D2010提供的DataSnap过滤器.可用于在客户端和服务

端压缩数据流.这里说的

ZlibCompression过滤器可以在DbxCompressionFilter单元找到.

TDSTCPServer和TDSHTTPService组件都有一个TTransportFiltersCollection

类型的Filters属性.点击Filters

属性后面的按钮,编辑过滤器列表.在这个对话框中,我们可以加一个新的

TTransportFilterItem,然后在Object

Inspector中设置FilterID和一些属性.在下拉框中可以找到Delphi2010提供

的ZLibCompression过滤器.

注意除了设置服务端TDSTCPServerTransport组件Filters属性外,也需要在客

户端指定一个相应的过滤器

(压缩请求解压缩应答).这时,我们进需要将DbxCompressionFilter单元引用到

ClientForm.其将自动注册一个

TTransportCompressionFilter并与服务端通讯.

如果没有在客户端添加DbxCompressionFilter单元引用,运行客户端后将会抛

出异常信息:

4.2. LOG FILTER

Delphi 2010 DataSnap 允许自定义传输过滤器.我们可以从TTransportFilter

类型继承自己的类.在这个新

类中,可以重写基类中的方法,实现这些方法.例如我们创建一个TLogFilter类:

unit LogFilter;

interface

uses

SysUtils, DBXPlatform, DBXTransport;

type

TLogFilter = class(TTransportFilter) ;

private

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;

const

LogFilterName = 'Log'

这个类的很多方法实现都是空的:由于仅仅用于记录ProcessInput和

ProcessOutput方法传输的数据,很多方

法都不用实现.非空方法如下:

function ameterValue(const ParamName, ParamValue:

UnicodeString): Boolean;

begin

Result := True;

end;

constructor ;

begin

inherited Create;

end;

destructor y;

begin

inherited Destroy;

end;

function sInput(const Data: TBytes): TBytes;

begin

Result := Data; // log incoming data

end;

function sOutput(const Data: TBytes): TBytes;

begin

Result := Data; // log outgoing data

end;

function : UnicodeString;

begin

Result := LogFilterName;

end;

最后,重要的实现部分是在initialization和finalization中注册DataSnap

传输过滤器.确保客户端可以找到这个传

输过滤器,并在请求时自动使用.

initialization

erFilter(LogFilterName, TLogFilter);

finalization

sterFilter(LogFilterName);

end.

为了在DataSnap服务端使用这个传输过滤器,我们需要将其加入到

TDSTCPServer或DSHTTPService组件

的Filters属性中,非常简单.在设计时,已知存在一个ZLibCompression过滤器,

但无法感知新的过滤器(除非将其

添加到设计时包中并安装).幸运的是我们也可以在运行时添加过滤器,在

ServerContainerUnitDemo单元中引用

过滤器单元,然后手动向Filters属性中添加过滤器.如.

procedure duleCreate(Sender: TObject);

begin

ter(LogFilterName);

ter(LogFilterName);

:= True;

end;

这将确保服务端使用LogFilter,客户端只要在单元中引用LogFilter单元就会

自动使用LogFilter.否则将抛出

错误信息:

注意DataSnap客户端和服务端将获取各自的logfile实例.虽然使用的同一个过

滤器,但是却不需用

ParamStr(0)来区分日志信息.

4.3. 加密过滤器

即使是一个如4.2中的一个简单的过滤器,也需要自己去扩展,很复杂.DataSnap

提供的过滤器不太完整.事实

上,有大量的三方过滤器可以使用,有Daniele Teti开发的DataSnap Filters

Compendium可以

在/?p=168中获取,其中不少于9个用于

DataSnap2010的附加过滤器,分成三个

组.Hash组支持MD5,MD4,SHA1和SHA512,Cipher组支持Blowfish,Rijndael,

3TDES 和 3DES, Compress

组支持LZO.并且是全源码版.

5. 如何构建DATASNAP WEB项目

除了生成Windows项目,还提供了生成ISAPI,CGI或Web App Debugger目标项

目的向导.首先我们讨论

一下每种项目类型的优缺点,并展示如何在一个项目组中同时创建出这三个项

目,并让他们共享公共的单元

文件.这样我们就可以为同一个DataSnap项目产生三个不同的部署目标.

虽然到目前为止我们构建的DataSnap服务应用程序运行良好.但是在有些情况

下就无法部署服务程序.例

如如果你不能或不允许在防火墙中打开请求的端口让客户端连接服务器.幸运的

是,这种情况下我们可以使

用Web服务来部署,而80端口号一直都是打开的.如果我们使用IIS来作为Web

服务器,我们就可以使用新的

DataSnap WebBroker应用程序向导来创建一个可部署在IIS上的应用.

DataSnap WebBroker应用程序向导提供了三个选项,第一个选项实际上不是真正

的WebBroker应用,但

仅仅Web App Debugger可执行文件,这是用于调试目的的. Web App Debugger

可执行文件很强大,允许我

们使用Web App Debugger(Delphi IDE Tooles菜单中)作为调试Web App Debugger

应用程序的宿主程序.

调试CGI或ISAPI/NSAPI Web应用非常不方便,因此在开发过程中最好选择Web

App Debugger模式.

而ISAPI/NSAPI Dynamic Link Library 和 CGI Stand-alone executable 类型

项目可用于真正部署的

DataSnap服务项目上.

注意,选择CGI Stand-alone executable不是一个好主意,因为这个可执行文件

将在每次请求中加载卸载.

在加上要连接到数据库执行一些任务,你必须要考虑到应用程序的执行效率.使

用ISAPI的DLL形式,只需要

加载一次,保存在内存中,后续请求(可能来自其他用户)不需要再次加载.ISAPI

DLL的主要缺点是升级困难

(如果你用FTP连接到Web服务器).但可以联系Web服务供应商.

ISAPI DLL的另一个缺点是调试不方便—必须用IIS作为宿主应用,不能总是按

计划的那样运行.但可以用

Web App Debugger executable来解决这个问题—同时创建两个项目,他们使用

公共的DataSnap方法和代

码.第一个范例就是这种形式,加入一些实用的功能构建一个框架.

5.3. 服务方法,部署,客户端

增加功能时只需要修改被两个项目共享的单元.默认有

一个范例函数,就是上面的

范例,我们包含多个方法(组件说明和源码见2.1.4.).服务端方法实现后,我们

就可以将ISAPI DLL部署到IIS.

详细信息可见

/jimtierney/2009/08/20/31502.

本例中如你没有Web服务可用于部署,可以使用我已部署的DataSnap ISAPI.注

意我没有发布

TDataSetProvider,也没有实现返回数据的GetEmployees方法,但是ServerTime

和EchoString方法都运行

良好,可以用来测试DataSnap客户端了.

在客户端连接ISAPI DataSnap服务端前,可以使用Data Explorer检查一下是否

可以连接到ISAPI

DataSnap服务. Data Explorer上有一个新的叫做DATASNAP的目录,展开后,第

一个连接叫做

DataSnapConnection,右键修改连接.在这个对话框中我们可以选择协议,主机

(如你没有自己的Web服务可

以使用),端口号,以及ISAPI DataSnap服务应用程序在Web服

务器上的URL路径,这里是

cgi-bin/.点击测试连接.

点击OK关闭窗口,在Data Explorer,展开DATASNAPCONNECTION节点查看表,视

图,过程,函数和

同义词.下图中,过程包括DSAdmin,DSMetaData,_XXX及我

们自定义的三个函数

EchoString,ServerTime 和 GetEmployees.

不需要写DataSnap客户端,现在就可以测试这些方法.例如EchoString方法(发

送什么返回什么).右键点击

loyees方法,选择View Parameters,弹出一个新窗口,

输入参数(例如42).在这个新

窗口中右击,选择”执行远程服务方法”.运行结果将显示在ReturnValue中.

这样我们就可以调用远程DataSnap服务方法.为了在客户端连接到远程服务端,

我们只需要修改

TSQLConnection组件的属性.原来我们连接都Windows版本的DataSnap服务,现

在我们需要修改设置连接到

Web版本.

注意,如果你使用的是我发布的,我已经禁用了

TDataSetProvider,而且GetEmployees方法

不返回任何数据.但你可以使用ServerTime 和 EchoString方法.

6.如何使用REST和JSON

DataSnap2010支持REST和ap2010特性REST支持DataSnap HTTP

请求.例如,如果

DataSnap服务的URI是/cgi-bin/.

我们可以在此URL后

加 /datasnap/rest,后跟服务类名称,方法名称和参数.语法如下:

server/datasnap/rest///

对于我的服务器上的TServerMethod1模块中的ServerTime方法,URL如下:

/cgi-bin//datasnap/rest/TServe

rMethods1/ServerTime

在浏览器中输入这个REST支持的URL,如下图:

在浏览器中返回结果是JSON结构:

{"result":["2009-10-16 16:01:33.145"]}

更多信息见Marco Cantù的Delphi2010和REST客户端白页.

6.1. 回调

除了用REST支持调用DataSnap服务方法外,JSON还用于实现回调方

法.DataSnap2010支持客户端回调

函数,使其执行在服务方法上下文中.这样就可以实现客户端调用服务端方法时,

服务端就可以调用由客户端传

递好参数的回调函数.

例如,我们修改EchoString方法,向其中添加回调支持.修改后的EchoString方

法如下:

function EchoString(Value: string; callback: TDBXcallback): string;

TDBXcallback类定义在DBXJSON单元.在我们实现EchoString方法前,先搞清楚

如何在客户端定义回调函

数(毕竟,这是一个可以让服务端调用的客户端方法).

在客户端,我们必须定义一个新类,继承在TDBXCallback,重写其Execute方法.

type

TCallbackClient = class(TDBXCallback)

public

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

end;

在Execute方法中,有一个TJSONValue类型的参数,可以复制(Clone)这个参数然

后设置其具体内

容.Execute方法也返回一个TJSONValue类型的值,这里我们只返回同样的值:

function e(const Arg: TJSONValue): TJSONValue;

var

Data: TJSONValue;

begin

Data := TJSONValue();

ShowMessage('Callback: ' + TJSONObject(Data).Get(0).);

Result := Data

end;

例如, 在方法实际返回前(如方法正在执行),回调函数将显示EchoString方法

传递参数的值.服务端新的

EchoString方法实现需要将String值赋给一个TJSONObject对象,并将其传递

给回调函数.如下:

function ring(Value: string; callback:

TDBXcallback): string;

var

msg: TJSONObject;

pair: TJSONPair;

begin

Result := Value;

msg := ;

pair := ('ECHO', Value);

:= True;

r(pair);

e(msg);

end;

注意这个回调函数将在客户端执行—然后在服务端Echostring方法执行完毕前

返回

最后,在客户端调用EchoString方法也需要修改,因为我们现在要提供一个回调

类TCallbackClient的实例,如

下所示:

var

MyCallback: TCallbackClient;

begin

MyCallback := ;

try

ring(, MyCallback);

finally

;

end;

end;

这个范例阐释了如何在DataSnap2010中使用客户端回调函数.

7. 使用DATASNAP 和 .NET

Delphi Prism 2010可用来构建使用我们先前生成Wind32服务的DataSnap .NET

客户端.为了构建Delphi

Prism 2010 DataSnap客户端,先确保DataSnap服务正常运行.

启动Delphi Prism 2010,点击View. Server Explorer启动Delphi Prism Server

Explorer.首先建立一个连

接,验证我们将以使用的DataSnap服务.

Server Explorer的根节点叫做Data Connections.右击Data Connections选择

添加连接.对话框如下,在

Data Sources列表中选择DataSnap(注意如果数据源已经预选好了我们需要点击

变更一下)

不要选中 Always use this selectiong.除非你一直构建DataSnap数据连接.

点击Continue按钮进入下一步.指定连接的DataSnap服务详细信息.在协议下拉

框中选择TCP/IP或HTTP.

接下来,指定服务器(运行DataSnap服务的主机名称,如在本机测试可指定

localhost),然后指定端口号.默认

HTTP为80端口,TCP/IP为211端口.但从本白页中可知这两个端口都应该修改,

并确保和你在

ServerContainerUnitDemo单元中设置的端口号对应.下一个属性包含路径,这

在你要连接到基于Web

Broker的DataSnap服务上很重要.设置为后面的部分.

最后,不要忘记验证用户和密码,本例DataSnap服务使用HTTP验证.

点击测试按钮,验证连接.如果弹出连接成功信息表示连接可用.

点击OK按钮,在连接树中显示了一个新的DataSnap连接.本例中是localhost

节点.展开这个节点,显示表,

视图,存储过程子节点.表和视图节点为空,但存储过程节点包括所有在

DataSnap服务端定义的服务方法.包

括我们自定义的EchoString,GetEmployees和ServerTime.

我们现在可以在Server Explorer中测试服务方法.例如,右击EchoString方法,

选择查看参数.弹出新窗口,

输入参数.这里输入42.右击窗口选择执行.将执行服务端的EchoString方法.如

下图.

更好的是可以使用GetEmployees方法演示如何从Employees表中获取数据.这个

存储过程没有参数,但还

有选择 View Parambers命令,返回一个空参数列表.右击窗体选择 执行.这是返

回一个记录集.如下图:

7.1. WINFORMS 客户端

虽然已经可以运行服务端方法了,但更有用的方法是在.NET应用程序中调用这

些方法.最后一个范

例, Project启动Delphi Prism新项目向导.选择项目类型.

在Windows 项目类型中选择Windows Application,修改WindowsApplication1

为DataSnapClient.

点击OK按钮,创建一个带有单元的新项目.

在Server Explorer,选择新建的DataSnap服务连接,属性框中找到

ConnectionString,如下:

communicationprotocol=http;hostname=localhost;port=8080;dsauthenticat

ionuser=Bob;ds

authenticationpassword=Swart

右击数据连接节点,选择生成客户端代理(Generate Client Proxy)选项.生成新

文件,其中

定义了TServerMethods1Client类及其中的方法(EchoString, ServerTime, 和

GetEmployees)..如下:

TServerMethods1Client = class

public

constructor (ADBXConnection: TAdoDbxConnection);

constructor (ADBXConnection: TAdoDbxConnection; AInstanceOwner:

Boolean);

function EchoString(Value: string): string;

function ServerTime: DateTime;

function GetEmployees: eader;

除了代理类还在项目的引用节点中添加了Client 和

entDriver

引用.

从TServerMethods1Client类代码片段中可见,类有两个构造函数:有使用了一

个ADBXConnection参数,

第二个构造函数还有一个AInstanceOwner的Boolean类型参数.这意味着我们必

须使用参数调用构造函数.

为了支持这个功能,必须修改项目属性设置.在解决方案管理器中右击

DataSnapClient,选择属性.如下图,点

击Compatibility标签,选中”Allow Create constructor calls”,将允许我们

调用.Create构造方法,传递参数,而不

仅仅是使用new关键字.

现在回到主窗体,添加一个按钮.在Click事件中创建一个DataSnap服务连接并

调用方法.

method 1_Click(sender: ; e:

rgs);

var

Client: rMethods1Client;

Connection: xDatasnapConnection;

begin

Connection := new xDatasnapConnection();

tionString :=

'communicationprotocol=http;hostname=localhost;port=8080;dsauthentica

tionuser=Bob;dsauth

enticationpassword=Swart';

;

try

Client := (Connection);

(

ring('Delphi Prism 2010'));

finally

;

end;

end;

运行结果如下图所示:

同样方式,我们调用GetEmployees方法获取结果集并显示到DataGridView.这里

有个小问题,

DataGridView方法返回的是IDataReader(等价于TSQLDataSet结果集),而不是

DataSet和DataTable.我们

必须写几行代码将GetEmployees返回的结果集保存到DataSet的DataTabl中

(等价于Win32中的

TClientDataSet).

method 1_Click(sender: ; e:

rgs);

var

Client: rMethods1Client;

Connection: xDatasnapConnection;

Employees: eader;

ds: t;

dt: ble;

begin

Connection := new xDatasnapConnection();

tionString :=

'communicationprotocol=http;hostname=localhost;port=8080;dsauthentica

tionuser=Bob;dsauthenticationpassword=Swart';

;

try

Client := (Connection);

Employees := loyees;

ds := new DataSet();

dt := new DataTable("DataSnap");

(dt);

(Employees, veChanges, [0]);

urce := [0];

(

ring('Delphi Prism 2010'));

finally

;

end;

end;

Delphi 2010 DataSnap白皮书1

在这个白页中我们将讲解Delphi2010 DataSnap架构新的特性和功能.

1. DATASNAP 历史

作为MIDAS起始于Delphi3,Delphi4是MIDAS II,Delphi5中是MIDASIII,而后基于COM远程数据模块方式使

用TCP/IP,HTTP,(D)COM构建出强大的通讯能力.从Delphi6开始改名为DataSnap,直到D2007这个框架一直在使

用.D2009重新架构了DataSnap—移除COM依赖,使用TCP/IP以更轻量级的方式生成远程服务对象和客户端连接

能力.同时提供了与Delphi Prism2009开发的.NET程序通讯的功能.

Delphi2010中构建于D2009架构之上,并对此架构做了进一步的扩展,包括使用两个向导来创建新的

部署目标(VCL窗体,Window服务,控制台及面向Web的ISAPI,CGI或Web App Debugger).HTTP(S)传输协

议,HTTP验证,客户端回调函数,REST和JSON的支持,及使用过滤器来支持压缩和解压缩.

1.1 DATASNAP范例数据位置

本白页中我建议您使用Demo和范例来学习.虽然Delphi支持很多数据库系统,使用DBX4,ADO dbGo,或

其他数据存取技术,为了演示方便我这里使用DBX4来操作BlackfishSQL的数据库.见

[ C:Documents and SettingsAll UsersDocumentsRAD

].在截图中可以看到我使用的

是Windows Vista或Win7操作系统,使用Windows Server 2008 Web编辑器来部署DataSnap ISAPI服务.

2. DATASNAP目标:如何获取数据

DataSnap2010支持三种不同的Windows方式:VCL窗体,Windows服务和控制台应用程序.本节中我们

将讨论他们的好处,不同和每种方式最适合在什么情况下使用.

下面会创建一个DataSnap服务端和客户端,我们将讲解

TDSServer,TDSServerClass,TDSTCPServerTransport,TDSHTTPService,TDSHTTPWebDispatcher和

TDSHTTPServiceAuthenticationManager组件,以及自定义的服务方法和TDSServerModule类.

将讨论不同的传输协议(TCP,HTTP)的好处及传输效率.并讨论DataSnap服务对象的不同生命期选项

(Server,Session,Invocation),及他们的效率和使用的建议.最后,讨论部署.

2.1. DATASNAP 服务端范例

在Object Repository中有两个不同的DataSnap服务向导:一个是生成基于Windows的Datasnap服务

项目,一个是生成基于WebBroker的DataSnap服务项目(需要部署到IIS或Apache).我们将会演示.

启动了Delphi2010,点击,你会在Object Repository中看到DataSnap服务向导中

显示的三个图标:DataSnap Server,DataSnap WebBroker Server,和Server Module.

双击第一个(后面的两个在下面的小结中讲解),弹出如下对话框:

界面中第一部分是控制项目类型的.默认可以生成可视化的带有主窗体的VCL窗体应用程序.第二个

选项是创建控制台应用程序,生成一个控制台窗口—可以用来输出请求应答信息(用Writeln语句输出

服务应用程序正在做什么).这两种方式都是为了做范例或最初部署,很少用于最终部署.由于

DataSnap架构不再基于COM,客户端将不能使服务端启动.因此为了响应客户端的请求,DataSnap服务

端应该一直在运行.如果你希望应用7X24小时全天候运行,DataSnap服务端必须同时也在运行中.对应

VCL窗体或控制台应用程序,需要一个账户登录到Windows中后才能启动DataSnap服务,背离了这种要

求.第三者选择在这时最适合:一个Windows服务应用程序,安装后配置成为自动启动,当计算机启动后

将自动运行(不需要账户登录).服务应用程序不会弹出界面,很难调试Bug.然而,为了整合这三种的优

势,我将用几分钟创建一个项目组,包括VCL窗体应用程序的DataSnap服务,控制台DataSnap服务,及

Windows服务Datasnap服务,都共享同一个自定义的服务方法,这样就可以开发一个Datasnap服务应用

程序,在需要的时候编译出三个不同类型的部署方式.

第二部分是选择使用的Datasnap服务的通讯协议.和DanaSnap2009相比,我们可以看到多了一个

HTTP通讯,及HTTP验证.为了更加灵活,这里建议选择全部选项,我们可以同时使用TCP/IP,HTTP,及使

用HTTP引入的HTTP验证.

第三部分已经为我们配置好了,如果我们要提供一个服务方法类,我们可以选择它的基

类:TPersistent,TDataModule或TDSServerModule.推荐使用最后的一个选项,可使用RTTI来启动执行

函数 (也可能你觉得使用TDataModule更合适—不操作数据库,或不使用其他非可视控件,这时使用

TPersitent也够用了).

现在是从中贴出来的一小段代码,来说明TDSServerModule和

TProviderDataModule(也是继承于TDataModule)之间的关系.

TDSServerModuleBase = class(TProviderDataModule)

public

procedure BeforeDestruction; override;

destructor Destroy; override;

end;

{$MethodInfo ON}

TDSServerModule = class(TDSServerModuleBase)

end;

{$MethodInfo OFF}

当无法确定时就使用TDSServerModule选项作为基类.

2.1.1. 创建多目标项目组-- VCL 窗体项目

如上面所说,这里创建多目标的Datasnap服务项目组.首先创建一个VCL窗体应用程序作为Datasnap

服务,选择所有的通讯协议.

默认创建了一个叫做的项目,并带有三个单元文

件,,和.首先 Project As保

存项目,并输入有实际意义的文件名称.将保存为,

保存为文件,保存为.

稍后我们将向项目组添加控制台应用程序和Window服务应用程序.首先我们来检查一下项目,并编

译工程.如果你编译DataSnapServer项目,将会出现一个错误信息(由于我们将

改名所致).错误原因是由于单元中的

Implementation部分引用了单元.为了修复这个冲突,修改引用单元的文件

名称,从新编译.这是发现在第37行出现错误,使用了ServerMethodsUnit1中的TServerMethods1类型.

修改ServerMethodsUnit1为ServerMethodsUnitDemo.这时可以正确的编译项目了.

ServerContainerUnitDemo的引用部分应该向下面代码所示:

implementation

uses

Windows, ServerMethodsUnitDemo;

{$R *.dfm}

procedure erClass1GetClass(

DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);

begin

PersistentClass := rMethods1;

end;

end.

2.1.1.1. SERVERCONTAINERUNITDEMO

打开ServerContainerUnitDemo单元,将会看到不少于五个组件:一个TDSServer,一个

TDSServerClass,一个TDSTCPServerTransport(用于TCP/IP通讯),一个TDSHTTPService(用于HTTP

通讯),一个TDSHTTPServiceAuthenticationManager组件(用于HTTP验证).

前面两个一直会存在,其他的三个则是根据选择的通讯协议生成的.

2.1.1.1.1. TDSSERVER

TDSServer组件只有四个属性,AutoStart,HideDSAdmin,Name和art属性默认设置为

True,意味着在窗体创建后自动启动DataSnap服务.如果将AutoStart设置为False,需要手动调用

Start方法启动服务,并调用Stop方法停止服务.可以调用Started方法验证DataSnap服务是否已经

启动.

HideAdmin属性默认设置为False.如果设置为True,连接到DataSnap服务的客户端将无法调用

Datasnap服务中的TDSAdmin类的内置方法.TDSAdmin不是一个真正的类,我们可以调用的TDSAdmin

方法定义在DSNames单元:

TDSAdminMethods = class

public

const CreateServerClasses = 'ServerClasses';

const CreateServerMethods = 'ServerMethods';

const FindClasses = 'asses';

const FindMethods = 'thods';

const FindPackages = 'ckages';

const GetPlatformName = 'tformName';

const GetServerClasses = 'verClasses';

const GetServerMethods = 'verMethods';

const GetServerMethodParameters = 'verMethodParameters';

const DropServerClasses = 'rverClasses';

const DropServerMethods = 'rverMethods';

const GetDatabaseConnectionProperties = 'abaseConnectionProperties';

end;

TDSServer组件有五个事件:OnConnect,OnDisconnect,OnError,OnPrepare和OnTrace.我们可以

实现这五个事件来响应不同的情况,例如向日志文件中写入日志.

OnConnect,OnDisconnect,OnError和OnPrepare事件有一个继承于TDSEventObject的参数,包含

了DxContext,传输,服务和DbxConnection组件的属性,在OnConnect和OnDisconnect事件中

TDSConnectEventObject类型还包含了ConnectionProperties和ChannelInfo属性.

TDSConnectEventObject也包括了由错误引起的异常, TDSConnectEventObject还包括了我们要使

用的MethodAlias和ServerClass属性.

OnTrace事件有一个TDBXTraceInfo类型的参数.注意由于这个OnTrace事件处理程序也会包含一

些代码错误,如TDBXTraceInfo和CBRType是编译器未知的.为了解决这个问题,我们需要引用

DBXCommon单元(为识别TDBXTraceInfo类型)和DBComonTypes单元(为识别CBRType类型).

在OnConnect事件处理中,我们可以通过ChannelInfo来查看连接信息,例如(使用自定义的函数

LogInfo向日志文件中写入信息):

procedure er1Connect(

DSConnectEventObject: TDSConnectEventObject);

begin

LogInfo('Connect ' + );

end;

在OnTrace事件处理程序中我们可以使用e中的信息记录服务端正在做什么.

function er1Trace(TraceInfo: TDBXTraceInfo): CBRType;

begin

LogInfo('Trace ' + Category);

LogInfo(' ' + e);

Result := cbrUSEDEF; // take default action

end;

注意,在客户端也可以使用连接到TSQLConnection组件的TSQLMonitor组件来跟踪DataSnap服务

端和客户端之间的通讯(在创建这个DataSnap服务的客户端时讲解).

一个跟踪日志输出如下所示:

17:05:55.492 Trace

17:05:55.496 read 136 bytes:{"method":"reader_close","params":[1,0]}

{"method":"prepare","params":[-1,false,"Method",

"_GetRecords"]}

17:05:55.499 Prepare

如你所见,e中包括了传输信息的字节数和被调用的方法名称等信息.

2.1.1.1.2. TDSSERVERCLASS

TDSServerClass组件将服务端特定的类发布给远程客户端(使用动态方法调用).

TDSServerClass组件有一个Server属性指向TDSServer组件.其他除了Name和Tag外的重要属

性是LifeCycle.默认是Session,但是也可设置为Server或Invocation.从长到短,Server,Session

和Invocation的意思是一个类的实例在服务端的生命周期为整个服务,一个DataSnap会话或一次

方法调用.Session表示每个连接将获取其自己的服务类实例.如果将其改为Invocation,将会得到

一个无状态的服务类—可用于部署CGI Web服务应用程序(其也是无状态的,每个请求都进行加载

卸载).将LifeCycle改为Server,则所有的连接请求使用一个服务类实例.这可以用于计算请求数

量,但是必须自己保证线程安全.

TDSServerClass有四个事件:OnCreateInstance,OnDestroyInstance(当实例创建和注销时触

发).OnGetClass和are事件可用于准备服务方法.使用D2009或使用D2010手动向

容器中添加TDSServerClass时,OnGetClass事件必须由我们自己实现,以便于指定一个可远程调用

的类.在D2010的向导中,已经自动为我们实现了OnGetClass事件,如下:

procedure erClass1GetClass(

DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);

begin

PersistentClass := rMethods1;

end;

注意:当我们重命名了自动生成的代码单元ServerMethodsUnit1为

后必须修改这里.

2.1.1.1.3. TDSTCPSERVERTRANSPORT

TDSTCPServerTransport组件负责在DataSnap服务端和客户端进行通讯,使用TCP/IP协议.

TDSTCPServerTransport组件有五个重要的属性:BufferKBSize,Filters(D2010新特

性),MaxThreads,PoolSize,Port和Server.

BufferKBSize属性指定通讯缓冲区大小,默认设置为s属性可以包含一个传输过滤

器集合,将在第四节讲解.MaxThreads属性定义最大线程数(默认为0不限制).PoolSize可用于连接

池(如果修改了这里,也需要相应的修改DataSnap客户端).

Server属性指向TDSServer组件.TDSTCPServerTransport组件没有事件.

2.1.1.1.4. TDSHTTPSERVICE

TDSHTTPService组件负责使用HTTP协议组织DataSnap服务端和客户端通讯.

TDSHTTPService组件有十个属性(除了Name和

Tag):Active,AuthenticationManager,DSHostName,DSPort,Filters,HttpPort,Name,RESTContext,Server,和只读的ServerSoftware属性.

Active属性指定DSHTTPService开始侦听请求.可以在设计时设置,但是这会影响DataSnap服务

在运行时启动(由于DSHTTPService组件侦听了同一个端口—在设计时启动侦听,在运行时就不能

再启动一个侦听了).最好的方式在在TServerContainer的OnCreate事件中激活TDSHTTPService:

procedure duleCreate(Sender: TObject);

begin

:= True;

end;

AuthenticationManager属性用于定义处理HTTP验证的管理组件,这里指向了

TDSHTTPServiceAuthenticationManager组件.这个组件在下节详述.

DSHostName和DSPort属性用于定义DataSnap服务端连接,但只有在没有指定Server属性时生效.

通常都是使用Server属性.

Filters属性可以包含一系列传输过滤器,在第四节详述.

HttpPort属性定义DSHTTPService组件侦听的特定端口以响应连接.注意这个属性默认是80端口,

通常在发布时必须做修改(IIS等Web服务已占用了80端口).

RESTContext属性指定REST上下文URL,这样就可以以REST服务的方式调用DataSnap服务.默

认,RESTContext属性设置为rest,我们可以用localhost/datasnap/rest/...来调用服务.

在第六节详述REST,JSON和客户端回调函数.

最后,Server属性指向同一个容器中的TDSServer组件.如果没有指定Server属性,也可以使用

DSHostName和DSPort属性连接到使用TCP的DataSnap服务.当设置了Server属性,DSHostName和

DSport属性失效.

TDSHTTPService组件有五个事件:四个是REST相关的,一个是跟踪事件,REST相关事件将在第六

节详述.

OnTrace事件可用于跟踪对DSHTTPService组件的调用,例如:

procedure Service1Trace(Sender: TObject;

AContext: TDSHTTPContext; ARequest: TDSHTTPRequest;

AResponse: TDSHTTPResponse);

begin

LogInfo('HTTP Trace ' + ng);

LogInfo(' ' + nt);

LogInfo(' ' + seText);

end;

注意HTTP跟踪信息只有当客户端使用HTTP连接到服务端是才会触发(默认使用TCP/IP协议).

跟踪输出如下所示:

17:05:55.398 HTTP Trace TDSHTTPContextIndy

17:05:55.400 /datasnap/tunnel

17:05:55.403 OK

从中可见,AContext设置为TDSHTTPContextIndy,ARequest设置为/DataSnap/tunnel,AResponse

设置为OK.

2.1.1.1.5. TDSHTTPSERVICEAUTHENTICATIONMANAGER

当选中HTTP通讯协议的验证复选框后,TDSHTTPServiceAuthenticationManager组件将自动出现

在服务容器中.也可手动添加到服务容器中,当然TDSHTTPService组件的AuthenticationManager

属性必须指向TDSHTTPServiceAuthenticationManager组件.

TDSHTTPServiceAuthenticationManager组件有一个事件:OnHTTPAuthenticate事件,可以验证

Datasnap客户端到服务端连接的HTTP信息.

procedure ServiceAuthenticationManager1HTTPAuthenticate(

Sender: TObject; const Protocol, Context, User, Password: string;

var valid: Boolean);

begin

if (User = 'Bob') and (Password = 'Swart') then

valid := True

else

valid := False

end;

当然,你可以使用数据库技术来扩展验证方式.客户端最好使用HTTPS方式将用户名和密码等

HTTP验证信息发送到服务端,所以我希望易博龙可以在现有HTTP和TCP/IP基础上在添加一个HTTPS

协议.

HTTPS可以确保连接安全和数据包加密, 数据包被窃取也不会泄露用户和密码信息.可与你所在

域的ISP或Web管理员协商是否有可能使用HTTPS—--强烈推荐(如我使用的

是).

DataSnap服务应用程序另一个优势是可将HTTP验证信息(所用协议和上下文信息)记录下来.以

便于查找谁登陆过,谁试图登录(如做欺骗登录操作试图获取DataSnap服务的权限).

2.1.1.2. SERVERMETHODSUNITDEMO

注意我们已经检查了单元,现在看一下DataSnap服务的另外重要

的单元:单元.在新建DataSnap服务对话框中我们指定了

TDSServerModule类作为基类,因此TServerMethods1类型继承于TDSServerModule(其继承于

TDSServerModuleBase,又继承于TProvideDataModule,添加一个析构函数和BeforeDestruction过

程. TProvideDataModule继承于正常的TDataModule,增加了服务提供者的能力 更多信息见后

面).

由于TServerMethods1继承于TDataModule,设计时可以看到一个数据模块的可视区域.我们可以

向其中添加一下不可视组件,如数据存取控件等.在第三节将操作数据库,现在保持设计区为空,仅

向TServerMethods1类添加方法.

如果不想向项目组添加其他类型的项目—DataSnap控制台应用程序及DataSnap Windows服务应

用程序,可跳到2.1.4节查看实现服务方法.

2.2. DATASNAP 客户端

第一个DataSnap服务端范例启动并侦听请求,现在创建一个客户端.本节中,我们将讲解如何在

客户端连接服务端,如何导入方法生产服务类.

确信DataSnap服务已启动,我们创建一个DataSnap客户端应用程序项目.为了在设计时方便切换

DataSnap服务项目和DataSnap客户端项目,将客户端项目添加到同一个项目组.任何类型的项目都

可作为DataSnap客户端项目,这里我们选择VCL窗口应用程序,保存为,主窗体

为.控件栏中DataSnap服务目录中含有六个DataSnap(服务)组件,DataSnap客户端

目录中不含我们现在需要的组件.

DataSnap客户端目录中的组件都是一些老的DataSnap组件,可以使用,但不推荐.但可使用新

的TDSProviderConnect组件,可以在新的DataSnap客户端上连接老的DataSnap服务(3.2中详述).

除了DataSnap客户端目录,我们还要看看dbEpress目录,可以找到一个新的组件叫做

TSQLServerMethod(注意:在下一个截图中这个新组件很容易发现,其用TSql前缀替代了TSQL前

缀).

TSqlServerMethod组件可用于调用DataSnap服务的远程方法,但首先需要连接到DataSnap服务.

可以使用TSQLConnect组件建立连接—--不在使用原来的TXXXConnection组件.为了灵活

性,TSQLConnection组件的Drive属性下拉框中有个新选项:DataSnap.在ClientForm上放置一个

TSQLConnection组件,设置其Driver属性为属性将变成可展开的对象,展开后如

下图:

注意:默认CommunicationProtocol属性为空,TSQLConnection将使用TCP/IP作为通信协议(端口

211).BufferKBSize为32(KB),端口设置为211(与服务端设置相同).实际应用中一般将端口号设置

为其他端口(同时修改服务端客户端),因为211端口是DataSnap默认端口不安全.

HostName,UserName和Password用于连接到DataSnap服务.在本地测试时,将HostName设置为

localhost,通常可以设置为服务器名称,DNS或IP地址.

不要忘记将TSQLConnection的LoginPropt属性设置为False,否则将会在连接的时候弹出登录窗

口.

一旦TSQLConnection的Driver属性设置好,就可以设置Connected属性为True去连接DataSnap服

务端.注意这时DataSnap服务必须运行才能连接成功.

2.2.1. DATASNAP客户端类

在确定连接正常后,右击TSQLConnection组件,选择Generate DataSnap Class选项,将生成一个

新单元,默认叫做Unit1,含有一个叫做TServerMethods1Client的类(在DataSnap服务方法类名称

后加了一个Client).保存单元为.

这个TServerMethods1Client类如下所示

type

TServerMethods1Client = class

private

FDBXConnection: TDBXConnection;

FInstanceOwner: Boolean;

FEchoStringCommand: TDBXCommand;

FServerTimeCommand: TDBXCommand;

public

constructor Create(ADBXConnection: TDBXConnection); overload;

constructor Create(ADBXConnection: TDBXConnection;

AInstanceOwner: Boolean); overload;

destructor Destroy; override;

function EchoString(Value: string): string;

function ServerTime: TDateTime;

end;

如你所见,TServerMethods1Client类有两个构造方法,一个析构方法和两个我们在服务端定义

的服务方法.

为使用这些方法,将ServerMethodsClient单元引用到ClientForm,在客户端窗体上放置一个按

钮,在按钮的OnClick事件中写如下代码:

procedure 1Click(Sender: TObject);

var

Server: TServerMethods1Client;

begin

Server := (nection);

try

ShowMessage(DateTimeToStr(Time))

finally

end;

end;

这个代码将创建一个TServerMethods1Client类实例,然后调用ServerTime服务类,最后释放这

个DataSnap服务的代理对象.

点击按钮将弹出对话框显示服务端的时间.

同样方法测试EchoString.

2.2.1.1. HTTP COMMUNICATION PROTOCOL

注意我已经提到TSQLConnection组件以TCP/IP作为默认通讯协议.这样我们就看不到任何HTTP

跟踪信息(在2.1.1.1.4小结中定义).然而,很容易修改通讯协议,仅需在TSQLConnection的Driver

属性中的CommunicationProtocal子属性中输入HTTP即可.注意这是我们还要修改Port属性,因为

211被TCP/IP占用,同时要修改服务端的TDSHTTPService组件的端口号.

修改后运行DataSnap客户端,会出现如下错误:

解决方法是在DataSnap客户端向ClientForm中添加DSHTTPLayer单元引用.

2.2.1.2. HTTP 验证

使用HTTP通讯协议的好处之一是可以使用其包含的HTTP验证.由DataSnap服务端的

TDSHTTPServiceAuthenticationManager组件所支持(详见2.1.1.1.5).

如果实现了OnHTTPAuthenticate事件处理,将会核对HTTP验证.我们必须保证输入正确的信息才

能确保TDSHTTPServiceAuthenticationManager验证通过.否则将会得到一个HTTP/1.1 401未验证

错误.

HTTP检验从客户端传递到服务端的用户名和密码,及其他TDSHTTPServiceAuthentication特定

信息,我们需要在DataSnap客户端的TSQLConnection控件中填写DSAuthUser和DSAuthPassword属

性.

注意我们也需要指定HostName的值,除非是在同一台电脑上测试.

2.3. DATASNAP服务部署

范例的服务端和客户端在同一台电脑上运行良好,但是实际环境中,DataSnap服务将运行在服务

器上,一或多台客户端通过网络连接服务端.服务端程序通常部署在没有安装Delphi的电脑上.这

种情况下就需要考虑不用运行时包来编译DataSnap,而仅生成一个大的可执行文件.由于我们还没

有使用任何数据操作组件,也不需要任何的其他数据库驱动或DLL文件.

2.3.1. DATASNAP 客户端部署

假设客户端与服务端运行在不同的电脑上,我们必须保证客户端可以连接到服务端.为了保证这

点,客户端的TSQLConnection组件不能仅仅指定Driver属性中的CommunicationProtocol和端口,

还需要指定HostName属性.设置IP地址或DNS.例如连接我的DataSnap服务的HostName属性

为.(注意不需要指定前缀,因为在CommunicationProtocol属性中指定了

通讯协议).

3. DATASNAP和数据库

除了使用Delphi2010 DataSnap框架创建简单的服务方法,我们还可以在服务端添加数据库操作,

实现多层数据库应用—DataSnap服务连接到数据库,但DataSnap客户端是瘦客户端,不含任何数据

库驱动.

我们同样可以快速创建数据库操作的DataSnap范例,只使用SQLConnection组件和已创建的客户

端类.也可使用另外两个DataSnap组件TsqlServerMethod和TDSProviderConnection.

首先,我们要确保服务端公布了一个数据集,打开ServerMethodsUnitDemo并在数据模块中添加

一个TSQLConnection组件.连接TSQLConnection组件到数据库和表(本例我连接到BlackFishSQL的

Employees表).将TSQLConnection组件添加到数据模块的设计区域.设置Driver属性为

BlachFishSql.然后设置Database属性为文件的路径: C:Documents and SettingsAll

UsersDocumentsRAD Studio7.0DemosdatabasedatabasesBlackfishSQL on Windows

XP, or C:UsersPublicDocuments RAD Studio7.0DemosdatabasedatabasesBlackfishSQL.

将TSQLConnection的LoginPrompt属性设置为False.设置Connected属性为True验证是否能正确连

接.

下一步,添加一个TSQLDataSet组件,将其SQLConnection设置为TSQLConnection组件.

设置其CommandType为ctQuery,双击CommandText属性输入SQL语句.

SELECT EMP_NO, FIRST_NAME, LAST_NAME, HIRE_DATE, JOB_COUNTRY FROM EMPLOYEE

确保在设计时设置了TSQLConnection组件的LoginPrompt和Connected属性为False,及

TSQLDataSet的Active属性为False.

现在在ServerMethodsUnitDemo单元的TServerMethods1类中添加一个public方法返回一个

TSQLDataSet组件.

type

TServerMethods1 = class(TDSServerModule)

SQLConnection1: TSQLConnection;

SQLDataSet1: TSQLDataSet;

private

{ Private declarations }

public

{ Public declarations }

function EchoString(Value: string): string;

function ServerTime: TDateTime;

function GetEmployees: TDataSet;

end;

如你所见,TServerMethods1类中定义了一个叫做GetEmployees的方法,实现如下:

function loyees: TDataSet;

begin

; // make sure data can be retrieved

Result := SQLDataSet1

end;

重新编译服务并运行.注意如你关闭了服务但无法重新编译,可能是DataSnap服务还在运行.

3.1. TSQLSERVERMETHOD

回到DataSnap客户端,TSQLConnection组件应该已经断开到DataSnap服务端的连接(如果仍然连

接,说明你没有重新编译服务端).重新将Connected设置为True,从服务端更新信息,并重新从服务

端生成客户端类(使用右键菜单).

为了重写原来的ServerMethodsClient单元,推荐从项目中移除原来的ServerMethodsClient后

在选择’Generate DataSnap Client Classes’方法.保存了新的单元文件后不需要修改客户端代

码.生成了新文件后,可以从中看到GetEmplorees方法.

type

TServerMethods1Client = class

private

FDBXConnection: TDBXConnection;

FInstanceOwner: Boolean;

FEchoStringCommand: TDBXCommand;

FServerTimeCommand: TDBXCommand;

FGetEmployeesCommand: TDBXCommand;

public

constructor Create(ADBXConnection: TDBXConnection); overload;

constructor Create(ADBXConnection: TDBXConnection;

AInstanceOwner: Boolean); overload;

destructor Destroy; override;

function EchoString(Value: string): string;

function ServerTime: TDateTime;

function GetEmployees: TDataSet;

end;

为使用GetEmployees方法获取TDataSet,我们可以使用TsqlServerMethod组件.将

TsqlServerMethod组件添加到客户端窗体,设置其SQLConnection属性为SQLConnection1,然后打

开ServerMethodName下拉框显示可用的方法:一些DSAdmin方法(可通过设置TDSServer的

HideDSAdmin属性为True禁止),接下来是三个DSMetaData方法,七个_XXX方法

(由原来的IAppServer提供),和最后我们自己的TServerMethods1的方法:EchoString,ServerTime,

和GetEmployees方法.

本例我们选择loyees作为ServerMethodName属性的值.根据

ServerMethodName属性值,SqlServerMethod控件获取查询结果记录集.

我们使用TDataSetProvider,TClientDataSet和TDataSource来使数据显示在TDBGrid中.

在客户端添加一个TDataSetProvider控件,将其DataSet设置为SqlServerMethod控件.下一步添

加一个TClientDataSet控件,将其ProviderName设置为DataSetProvider控件.设置其

RemoteServer属性为空—--这是用于原有DataSnap的方法,在新的DataSnap架构中不再使用.

最后,放一个TDataSource控件,设置其DataSet为TClientDataSet控件,然后就可以放TGBGrid和

TDBNavigator控件了.设置其DataSource为TDataScource组件,将可以在客户端窗体中看到数据.

为了在设计时验证连接,我们设置ClientDataSet的Active属性为True.将触发SqlServerMethod

的Active为True(开始检索数据,然后自动将Active设置为False),同时会将

ted设置为True连接到服务端.

这种方式连接一直保持,TCliendtDataSet和TSQLConnection的被激活,数据显示在TDBGrid中.

这种方式很容易以只读方式查看数据.注意这里说只读,因为TSQLServerMethod不允许

TDataSetProvide-TClientDataSet组合将客户端的数据变更传回服务端.

TSQLServerMethod是轻量级方式连接获取只读数据.这样,如果你需要获取DataSnap服务器上的

数据但不需要做修改,当前这种发布一个返回TSQLDataSet结果集方法的架构就很好.

然而,有时我们需要更新数据,这时就必须使用不同的方法获取数据.

3.2. TDSPROVIDERCONNECTION

如果我们想提交更新,我们就需要TDSProviderConnection组件与服务端的TDataSetProvider关

联,因此我们不但可以读取数据也可以修改数据.

首先,我们需要修改服务端数据模块,现有的只是返回一个TDataSet,我们必须添加一个实际的

TDataSetProvider,确保将其从DataSnap服务端发布到客户端.所以,回到ServerMethodsUnitDemo

单元放一个TDataSetProvider组件,设置其DataSet属性为TSQLDataSet.应该将TDataSetProvider

重命名,如dspEmplyees;

现在,重新编译DataSnap服务,运行.然后修改客户端.

3.2.1. TDSPROVIDERCONNECTION 客户端

为检索到发布的TDataSetProvider组件需要修改一下DataSnap客户端.将TSQLServerMethod和

TDataSetProvider组件从客户端窗体删除,添加一个TDSProviderConnection组件.设置其

SQLConnection属性为TSQLConnection组件,连接到DataSnap服务.我们也需要设置一个

ServerClassname属性值(很不幸不能选择只能输入).现在只能手动输入TDSServerModule的名字,

这里是TServerMethods1.

在前一个例子中,TClientDataSet只设置了一个ProviderName属性.然而,使用

TDSProviderConnection组件,我们必须首先设置其RemoteServer属性为TDSproviderConnection

组件,然后设置ProviderName属性(这个属性还为原来设置的DataSetProvider1,现在设置为

dsEmployeer----在服务端发布的TDataSetProvider组件的名称).

在ProvideName属性的下拉框中显示dspEmployees选项(在服务端的ServerDataModule单元中发

布的名称).

现在我们可以设置为True,在设计时查看数据.

设置为True,同时将ted置为True.注意在设计

时最好不要将这两个属性设置为True.首先,如果你在IDE中打开DataSnap客户端项目,将会试图连

接服务端,如果服务端没开启将失败.其次,如果在运行时启动应用程序,并且连接不可用,应用程

序将会抛出异常.从而应用程序不能使用本地数据,导致远程连接无效将不能使用应用程序.

最好的方式是使用菜单选项或按钮明确的设置TSQLConnection组件连接,TClientDataSet组件

进行获取数据.并提交包括username/password的信息,将在后面说明.现在确保

属性设置为False,同时ted属性为False.在客户

端窗体中放一个按钮,在OnClick事件中明确的打开TClientDataSet.

procedure 2Click(Sender: TObject);

begin

;

end;

现在添加代码提交数据变更,保存回服务器.

3.2.2 数据库更新

有两种方法将数据修改保存会服务端:自动和手动.都是调用一下方法,但是会自动调用或手动

调用,各有优缺点.

对于自动方式,我们可以使用TClientDataSet的数据修改时触发的OnAfterInsert,OnAfterPost

和OnAfterDelete事件.在事件处理程序中,实现很简单,调用TClientDataSet的ApplyUpdates方法,

发送变更,将Delta包发送到服务端保存回数据库.

procedure DataSet1AfterPost(DataSet: TDataSet);

begin

pdates(0);

end;

如果发生了更新错误,将会触发TClientDataSet的OnReconcileError事件,更多信息见3.2.3

手动方式发生更新也是使用TClientDataSet的ApplyUpdates方法.但是这时方法不在

OnAfterInsert,OnAfterPost和OnAfterDelete事件中执行.而是我们添加一个按钮让用户显示的提交更

新.

procedure ateClick(Sender: TObject);

begin

pdates(0);

end;

自动提交的好处当然用户不会忘记将变更保存回服务端.然而,缺点是无法提供Undo能力.一旦

提交数据就更新回了服务器.另外,如果使用手动提交,所有变更保存在客户端---TClientDataSet

组件的内存中.这样就允许用户Undo部分变更:使特定记录或全部记录放弃更新.点击更新按钮显

示调用ApplyUpdate方法.可能会导致用户忘记提交修改数据.我们应该在窗口关闭时添加代码检

查TClientDataSet中是否还有未提交数据(检查Count属性).

3.2.3. RECONCILE ERRORS

pdates方法有一个参数:应用更新时允许发生的最大错误数量.如果有

两个客户端连接到了DataSnap服务端,获取Employees数据并同时修改了第一行数据.依据目前为

止我们的实现,两个客户端都会使用TClientDataSet的ApplyUpdates将数据变更到DataSnap服务

端.如果都将ApplyUpdates的参数MaxErrors设置为0,则第二个客户端的提交将会停止.第二个客

户端应该使用一个大于0的参数指定允许的错误/冲突数.然而,即使第二个客户端将MaxErrors设

置为-1(不管有多少错误发生都继续提交后面的更新记录),都不会提交被第一个用户更新过的记

录.换句话说,你需要执行一系列冲突处理来解决这些更新已经被更新的记录或列的冲突问题.

幸运的是,Delphi提供了一个很强大的对话框来处理这个问题.当在DataSnap客户端需要做一些

冲突处理时,都可以使用这个对话框(或自己实现,但最终都是处理冲突问题).

使用Delphi提供的功能,,在Delphi文件子目录中选择Reconcile Error对话框

图标.

选中这个图标点击OK,保存为,加入到DataSnapClient项目.这个单元包括了定义

和实现更新错误对话框.来解决数据库更新错误.

ReconcileErrorForm窗体实例将按需要动态创建.那么如何使用这个特殊的ReconcileErrorForm窗体呢?

好,其实很简单.对于每个没有成功更新的记录,都会触发TClientDataSet的OnReconcileError事件.定义如

下:

procedure DataSet1ReconcileError(DataSet: TClientDataSet;

E: EReconcileError; UpdateKind: TUpdateKind;

var Action: TReconcileAction);

这个事件处理程序与四个参数,第一个是抛出错误的TClientDataSet,第二个参数是引发错误冲

突的原因,第三个参数是更新类型UpdateKind(insert,delete,modify),第四个参数是你要如何处

理冲突.可以返回如下枚举类型值:

- raSkip:不更新这条记录,但在变更日志中保留未提交的变更,下次提交在试.

-raAbort:取消记录冲突处理..

-raMerge:将更新记录与远程数据库记录合并,仅在客户端变更修改过的远程字段

-raCorrect:使用正确的值替换更新记录,这需要用户介入.

-raCancel:对本记录的修改全部放弃.回到初始值状态.

-raRefresh:对本记录修改全部放弃,但重新加载当前数据库的记录值.

关于ReconcileErrorForm不需要考虑全部执行选项.只需要做两件事件.一,在DataSnap客户端主窗体中

引用错误处理对话框单元.二,在OnReconcileError事件中写一行代码调用ReconcileErrorForm单元中的

HandleReconcileError全局函数. HandleReconcileError函数也有四个同样的参数,只需要按顺序传递即可.

如下所示:

procedure DataSet1ReconcileError(DataSet: TClientDataSet;

E: EReconcileError; UpdateKind: TUpdateKind;

var Action: TReconcileAction);

begin

Action := HandleReconcileError(DataSet, UpdateKind, E)

end;

3.2.4. 示范冲突错误

现在最大的问题是:实际工作中如何使用的冲突处理?为了测试,需要两个或更多DataSnap客户

端同时运行.为使用当前的客户端和服务端进行测试,需要执行如下步骤:

-启动服务端应用程序

-启动第一个客户端应用程序,点击链接按钮,获取数据

-启动第二个客户端应用程序,点击链接按钮,获取数据

-使用第一个客户端应用程序,修改第一行数据的FirstName列

-使用第二个客户端应用程序,修改第一行数据的FirstName列

-在第一个客户端应用程序中点击更新按钮

-在第二个客户端应用程序中点击更新按钮,这时将会发生一个或多个错误.因为第一个应用程

序已经修改了同一行的同一个列.引起冲突. OnReconcileError被触发.

-进入更新错误对话框,现在可以处理冲突(忽略(Abort),取消(Abort),合并(Merge),更正(Correct),取消

(Cancel),更新(Refresh)).测试一下Skip和Cancel的不同,及Correct,Refresh和Merge的不同.

Skip移动到下一行记录,忽略更新请求.但其更新将保留在更新日志中.Cancel也忽略更新请求

同时清除本记录所有以前的更新记录.

Refresh清除本记录所有的变更记录,并将数据库中的值作为当前记录的值.Merge试图将数据库

记录和更新记录合并.将变更提交到数据库.更新和合并的记录都不会再进行处理,记录已经与数

据库同步.

Correct是一个强大的选项,在事件处理中给你一个指定更新记录值的机会.需要写代码或弹出

对话框指定新值.

3.3. DATASNAP 数据库部署

部署一个使用数据库的DataSnap服务需要比部署一个简单DataSnap服务的步骤要多些.客户端,

没什么变化,还是一个瘦客户端,如将MidasLib加入到了项目引用就仅需部署一个单一的可执行文

件.

服务端,必须部署数据库驱动.及所选数据库依赖的驱动和文件.使用DBX4,确保发布

TSQLConnection组件和及文件(可在C:Documents and

SettingsAll Users DocumentsRAD

StudiodbExpress7.0 directory on Windows XP or in the C:UsersPublicDocumentsRAD

StudiodbExpress7.0中找到). 文件指定所用驱动, DriverPackageLoader及

MetaDataPackageLoader(通常指向同一个包).对于BlackFishSQL,使用的文件,需要

部署到服务端.更多关于部署BlashFishSQL信息见RAD Studio7.0 目录下的deploy_文件.

3.4. 重用已有的远程数据模块

如果你有一个远程数据模块类,也可以将其组合到新的DataSnap项目中来.但是必须要牺牲一些特性,尤其

是引入了COM.

首先,如果有一个你要迁移的DataSnap服务应用程序,而不仅仅是一个远程数据模块,你需要使用命令行

/unregister注销DataSnap服务.不做这步将无法注销远程数据模块.在远程数据模块单元,移除initialization区

域.如果还希望这个单元在D2007及一下版本重用,可以使用编译开关:

{$IF CompilerVersion >= 20}

initialization

(ComServer, TRemoteDataModule2010,

Class_RemoteDataModule2010, ciMultiInstance, tmApartment);

{$IFEND}

end.

从项目中移除UpdateRegistry函数或用编译开关修饰:

{$IF CompilerVersion >= 20}

class procedure UpdateRegistry(Register: Boolean;

const ClassID, ProgID: string); override;

{$IFEND}

最重要的变更—将项目转换为无COM依赖的DataSnap服务.-及移除类型库(.ridl文件)和类型库导入单元.这

无法通过编译器开关修改,因此需要为D2007及一下版本和D2009及以上版本分别生成一个项目文件.在

TRemoteDataModule类中放一个TDSServerClass组件.最后,将所有的自定义方法都转到

TRemoteDataModule单元的public节中.

4. DATASNAP 过滤器[FILTER]用法

本节将说明过滤器工作原理,及如何使用已存在的过滤器(如压缩)或创建新的DataSnap过滤器.DataSnap

过滤器是一个特殊的DLL,拦截通讯流,在整个过滤器链中操作通讯流.所以本例中我们可以使用压缩和解压缩

过滤器,或记录压缩等.

必须在客户端和服务端指定过滤器.在服务端,必须指定TDSTCPServerTransport组件的过滤器属性列表.

在客户端,必须在客户端项目中引用过滤器单元文件.对于客户端这就足够了,因为每个DataSnap过滤器都会

自动注册.

当处理OnConnect事件时,可以检查用于连接的已注册的过滤器,例如使用自定义的日志函数输入日志信息,

procedure er1Connect(

DSConnectEventObject: TDSConnectEventObject);

var

i: Integer;

begin

LogInfo('Connect ' + );

for i:=0 to -1 do

LogInfo(' Filter: ' +

ter(i).Id);

end;

4.1. ZLIBCOMPRESSION FILTER

作为范例,我们使用已随D2010提供的DataSnap过滤器.可用于在客户端和服务端压缩数据流.这里说的

ZlibCompression过滤器可以在DbxCompressionFilter单元找到.

TDSTCPServer和TDSHTTPService组件都有一个TTransportFiltersCollection类型的Filters属性.点击Filters

属性后面的按钮,编辑过滤器列表.在这个对话框中,我们可以加一个新的TTransportFilterItem,然后在Object

Inspector中设置FilterID和一些属性.在下拉框中可以找到Delphi2010提供的ZLibCompression过滤器.

注意除了设置服务端TDSTCPServerTransport组件Filters属性外,也需要在客户端指定一个相应的过滤器

(压缩请求解压缩应答).这时,我们进需要将DbxCompressionFilter单元引用到ClientForm.其将自动注册一个

TTransportCompressionFilter并与服务端通讯.

如果没有在客户端添加DbxCompressionFilter单元引用,运行客户端后将会抛出异常信息:

/gzcszzx/blog/item/

4.2. LOG FILTER

Delphi 2010 DataSnap 允许自定义传输过滤器.我们可以从TTransportFilter 类型继承自己的类.在这个新

类中,可以重写基类中的方法,实现这些方法.例如我们创建一个TLogFilter类:

unit LogFilter;

interface

uses

SysUtils, DBXPlatform, DBXTransport;

type

TLogFilter = class(TTransportFilter) ;

private

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;

const

LogFilterName = 'Log'

这个类的很多方法实现都是空的:由于仅仅用于记录ProcessInput和ProcessOutput方法传输的数据,很多方

法都不用实现.非空方法如下:

function ameterValue(const ParamName, ParamValue: UnicodeString): Boolean;

begin

Result := True;

end;

constructor ;

begin

inherited Create;

end;

destructor y;

begin

inherited Destroy;

end;

function sInput(const Data: TBytes): TBytes;

begin

Result := Data; // log incoming data

end;

function sOutput(const Data: TBytes): TBytes;

begin

Result := Data; // log outgoing data

end;

function : UnicodeString;

begin

Result := LogFilterName;

end;

最后,重要的实现部分是在initialization和finalization中注册DataSnap传输过滤器.确保客户端可以找到这个传

输过滤器,并在请求时自动使用.

initialization

erFilter(LogFilterName, TLogFilter);

finalization

sterFilter(LogFilterName);

end.

为了在DataSnap服务端使用这个传输过滤器,我们需要将其加入到TDSTCPServer或DSHTTPService组件

的Filters属性中,非常简单.在设计时,已知存在一个ZLibCompression过滤器,但无法感知新的过滤器(除非将其

添加到设计时包中并安装).幸运的是我们也可以在运行时添加过滤器,在ServerContainerUnitDemo单元中引用

过滤器单元,然后手动向Filters属性中添加过滤器.如.

procedure duleCreate(Sender: TObject);

begin

ter(LogFilterName);

ter(LogFilterName);

:= True;

end;

这将确保服务端使用LogFilter,客户端只要在单元中引用LogFilter单元就会自动使用LogFilter.否则将抛出

错误信息:

注意DataSnap客户端和服务端将获取各自的logfile实例.虽然使用的同一个过滤器,但是却不需用

ParamStr(0)来区分日志信息.

4.3. 加密过滤器

即使是一个如4.2中的一个简单的过滤器,也需要自己去扩展,很复杂.DataSnap提供的过滤器不太完整.事实

上,有大量的三方过滤器可以使用,有Daniele Teti开发的DataSnap Filters Compendium可以

在/?p=168中获取,其中不少于9个用于DataSnap2010的附加过滤器,分成三个

组.Hash组支持MD5,MD4,SHA1和SHA512,Cipher组支持Blowfish,Rijndael, 3TDES 和 3DES, Compress

组支持LZO.并且是全源码版.

5. 如何构建DATASNAP WEB项目

除了生成Windows项目,还提供了生成ISAPI,CGI或Web App Debugger目标项目的向导.首先我们讨论

一下每种项目类型的优缺点,并展示如何在一个项目组中同时创建出这三个项目,并让他们共享公共的单元

文件.这样我们就可以为同一个DataSnap项目产生三个不同的部署目标.

虽然到目前为止我们构建的DataSnap服务应用程序运行良好.但是在有些情况下就无法部署服务程序.例

如如果你不能或不允许在防火墙中打开请求的端口让客户端连接服务器.幸运的是,这种情况下我们可以使

用Web服务来部署,而80端口号一直都是打开的.如果我们使用IIS来作为Web服务器,我们就可以使用新的

DataSnap WebBroker应用程序向导来创建一个可部署在IIS上的应用.

DataSnap WebBroker应用程序向导提供了三个选项,第一个选项实际上不是真正的WebBroker应用,但

仅仅Web App Debugger可执行文件,这是用于调试目的的. Web App Debugger可执行文件很强大,允许我

们使用Web App Debugger(Delphi IDE Tooles菜单中)作为调试Web App Debugger应用程序的宿主程序.

调试CGI或ISAPI/NSAPI Web应用非常不方便,因此在开发过程中最好选择Web App Debugger模式.

而ISAPI/NSAPI Dynamic Link Library 和 CGI Stand-alone executable 类型项目可用于真正部署的

DataSnap服务项目上.

注意,选择CGI Stand-alone executable不是一个好主意,因为这个可执行文件将在每次请求中加载卸载.

在加上要连接到数据库执行一些任务,你必须要考虑到应用程序的执行效率.使用ISAPI的DLL形式,只需要

加载一次,保存在内存中,后续请求(可能来自其他用户)不需要再次加载.ISAPI DLL的主要缺点是升级困难

(如果你用FTP连接到Web服务器).但可以联系Web服务供应商.

ISAPI DLL的另一个缺点是调试不方便—必须用IIS作为宿主应用,不能总是按计划的那样运行.但可以用

Web App Debugger executable来解决这个问题—同时创建两个项目,他们使用公共的DataSnap方法和代

码.第一个范例就是这种形式,加入一些实用的功能构建一个框架.

5.3. 服务方法,部署,客户端

增加功能时只需要修改被两个项目共享的单元.默认有一个范例函数,就是上面的

范例,我们包含多个方法(组件说明和源码见2.1.4.).服务端方法实现后,我们就可以将ISAPI DLL部署到IIS.

详细信息可见/jimtierney/2009/08/20/31502.

本例中如你没有Web服务可用于部署,可以使用我已部署的DataSnap ISAPI.注意我没有发布

TDataSetProvider,也没有实现返回数据的GetEmployees方法,但是ServerTime和EchoString方法都运行

良好,可以用来测试DataSnap客户端了.

在客户端连接ISAPI DataSnap服务端前,可以使用Data Explorer检查一下是否可以连接到ISAPI

DataSnap服务. Data Explorer上有一个新的叫做DATASNAP的目录,展开后,第一个连接叫做

DataSnapConnection,右键修改连接.在这个对话框中我们可以选择协议,主机(如你没有自己的Web服务可

以使用),端口号,以及ISAPI DataSnap服务应用程序在Web服务器上的URL路径,这里是

cgi-bin/.点击测试连接.

点击OK关闭窗口,在Data Explorer,展开DATASNAPCONNECTION节点查看表,视图,过程,函数和

同义词.下图中,过程包括DSAdmin,DSMetaData,_XXX及我们自定义的三个函数

EchoString,ServerTime 和 GetEmployees.

不需要写DataSnap客户端,现在就可以测试这些方法.例如EchoString方法(发送什么返回什么).右键点击

loyees方法,选择View Parameters,弹出一个新窗口,输入参数(例如42).在这个新

窗口中右击,选择”执行远程服务方法”.运行结果将显示在ReturnValue中.

这样我们就可以调用远程DataSnap服务方法.为了在客户端连接到远程服务端,我们只需要修改

TSQLConnection组件的属性.原来我们连接都Windows版本的DataSnap服务,现在我们需要修改设置连接到

Web版本.

注意,如果你使用的是我发布的,我已经禁用了TDataSetProvider,而且GetEmployees方法

不返回任何数据.但你可以使用ServerTime 和 EchoString方法.

6.如何使用REST和JSON

DataSnap2010支持REST和ap2010特性REST支持DataSnap HTTP请求.例如,如果

DataSnap服务的URI是/cgi-bin/.我们可以在此URL后

加 /datasnap/rest,后跟服务类名称,方法名称和参数.语法如下:

server/datasnap/rest///

对于我的服务器上的TServerMethod1模块中的ServerTime方法,URL如下:

/cgi-bin//datasnap/rest/TServerMethods1/ServerTime

在浏览器中输入这个REST支持的URL,如下图:

在浏览器中返回结果是JSON结构:

{"result":["2009-10-16 16:01:33.145"]}

更多信息见Marco Cantù的Delphi2010和REST客户端白页.

6.1. 回调

除了用REST支持调用DataSnap服务方法外,JSON还用于实现回调方法.DataSnap2010支持客户端回调

函数,使其执行在服务方法上下文中.这样就可以实现客户端调用服务端方法时,服务端就可以调用由客户端传

递好参数的回调函数.

例如,我们修改EchoString方法,向其中添加回调支持.修改后的EchoString方法如下:

function EchoString(Value: string; callback: TDBXcallback): string;

TDBXcallback类定义在DBXJSON单元.在我们实现EchoString方法前,先搞清楚如何在客户端定义回调函

数(毕竟,这是一个可以让服务端调用的客户端方法).

在客户端,我们必须定义一个新类,继承在TDBXCallback,重写其Execute方法.

type

TCallbackClient = class(TDBXCallback)

public

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

end;

在Execute方法中,有一个TJSONValue类型的参数,可以复制(Clone)这个参数然后设置其具体内

容.Execute方法也返回一个TJSONValue类型的值,这里我们只返回同样的值:

function e(const Arg: TJSONValue): TJSONValue;

var

Data: TJSONValue;

begin

Data := TJSONValue();

ShowMessage('Callback: ' + TJSONObject(Data).Get(0).);

Result := Data

end;

例如, 在方法实际返回前(如方法正在执行),回调函数将显示EchoString方法传递参数的值.服务端新的

EchoString方法实现需要将String值赋给一个TJSONObject对象,并将其传递给回调函数.如下:

function ring(Value: string; callback: TDBXcallback): string;

var

msg: TJSONObject;

pair: TJSONPair;

begin

Result := Value;

msg := ;

pair := ('ECHO', Value);

:= True;

r(pair);

e(msg);

end;

注意这个回调函数将在客户端执行—然后在服务端Echostring方法执行完毕前返回

最后,在客户端调用EchoString方法也需要修改,因为我们现在要提供一个回调类TCallbackClient的实例,如

下所示:

var

MyCallback: TCallbackClient;

begin

MyCallback := ;

try

ring(, MyCallback);

finally

;

end;

end;

这个范例阐释了如何在DataSnap2010中使用客户端回调函数.

7. 使用DATASNAP 和 .NET

Delphi Prism 2010可用来构建使用我们先前生成Wind32服务的DataSnap .NET客户端.为了构建Delphi

Prism 2010 DataSnap客户端,先确保DataSnap服务正常运行.

启动Delphi Prism 2010,点击View. Server Explorer启动Delphi Prism Server Explorer.首先建立一个连

接,验证我们将以使用的DataSnap服务.

Server Explorer的根节点叫做Data Connections.右击Data Connections选择添加连接.对话框如下,在

Data Sources列表中选择DataSnap(注意如果数据源已经预选好了我们需要点击变更一下)

不要选中 Always use this selectiong.除非你一直构建DataSnap数据连接.

点击Continue按钮进入下一步.指定连接的DataSnap服务详细信息.在协议下拉框中选择TCP/IP或HTTP.

接下来,指定服务器(运行DataSnap服务的主机名称,如在本机测试可指定localhost),然后指定端口号.默认

HTTP为80端口,TCP/IP为211端口.但从本白页中可知这两个端口都应该修改,并确保和你在

ServerContainerUnitDemo单元中设置的端口号对应.下一个属性包含路径,这在你要连接到基于Web

Broker的DataSnap服务上很重要.设置为后面的部分.

最后,不要忘记验证用户和密码,本例DataSnap服务使用HTTP验证.

点击测试按钮,验证连接.如果弹出连接成功信息表示连接可用.

点击OK按钮,在连接树中显示了一个新的DataSnap连接.本例中是localhost节点.展开这个节点,显示表,

视图,存储过程子节点.表和视图节点为空,但存储过程节点包括所有在DataSnap服务端定义的服务方法.包

括我们自定义的EchoString,GetEmployees和ServerTime.

我们现在可以在Server Explorer中测试服务方法.例如,右击EchoString方法,选择查看参数.弹出新窗口,

输入参数.这里输入42.右击窗口选择执行.将执行服务端的EchoString方法.如下图.

更好的是可以使用GetEmployees方法演示如何从Employees表中获取数据.这个存储过程没有参数,但还

有选择 View Parambers命令,返回一个空参数列表.右击窗体选择 执行.这是返回一个记录集.如下图:

7.1. WINFORMS 客户端

虽然已经可以运行服务端方法了,但更有用的方法是在.NET应用程序中调用这些方法.最后一个范

例, Project启动Delphi Prism新项目向导.选择项目类型.

在Windows 项目类型中选择Windows Application,修改WindowsApplication1为DataSnapClient.

点击OK按钮,创建一个带有单元的新项目.

在Server Explorer,选择新建的DataSnap服务连接,属性框中找到ConnectionString,如下:

communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;ds

authenticationpassword=Swart

右击数据连接节点,选择生成客户端代理(Generate Client Proxy)选项.生成新文件,其中

定义了TServerMethods1Client类及其中的方法(EchoString, ServerTime, 和GetEmployees)..如下:

TServerMethods1Client = class

public

constructor (ADBXConnection: TAdoDbxConnection);

constructor (ADBXConnection: TAdoDbxConnection; AInstanceOwner: Boolean);

function EchoString(Value: string): string;

function ServerTime: DateTime;

function GetEmployees: eader;

除了代理类还在项目的引用节点中添加了Client 和entDriver

引用.

从TServerMethods1Client类代码片段中可见,类有两个构造函数:有使用了一个ADBXConnection参数,

第二个构造函数还有一个AInstanceOwner的Boolean类型参数.这意味着我们必须使用参数调用构造函数.

为了支持这个功能,必须修改项目属性设置.在解决方案管理器中右击DataSnapClient,选择属性.如下图,点

击Compatibility标签,选中”Allow Create constructor calls”,将允许我们调用.Create构造方法,传递参数,而不

仅仅是使用new关键字.

现在回到主窗体,添加一个按钮.在Click事件中创建一个DataSnap服务连接并调用方法.

method 1_Click(sender: ; e: rgs);

var

Client: rMethods1Client;

Connection: xDatasnapConnection;

begin

Connection := new xDatasnapConnection();

tionString :=

'communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;dsauth

enticationpassword=Swart';

;

try

Client := (Connection);

(

ring('Delphi Prism 2010'));

finally

;

end;

end;

运行结果如下图所示:

同样方式,我们调用GetEmployees方法获取结果集并显示到DataGridView.这里有个小问题,

DataGridView方法返回的是IDataReader(等价于TSQLDataSet结果集),而不是DataSet和DataTable.我们

必须写几行代码将GetEmployees返回的结果集保存到DataSet的DataTabl中(等价于Win32中的

TClientDataSet).

method 1_Click(sender: ; e: rgs);

var

Client: rMethods1Client;

Connection: xDatasnapConnection;

Employees: eader;

ds: t;

dt: ble;

begin

Connection := new xDatasnapConnection();

tionString :=

'communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;dsauthenticationpassword=Swart';

;

try

Client := (Connection);

Employees := loyees;

ds := new DataSet();

dt := new DataTable("DataSnap");

(dt);

(Employees, veChanges, [0]);

urce := [0];

(

ring('Delphi Prism 2010'));

finally

;

end;

end;