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的employee.jds数据库.见 [ C:\Documents and Settings\All Users\Documents\RAD Studio\7.0\Demos\database\databases\BlackfishSQL\employee.jds].在截图中可以看到我使用的 是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,点击File.New.Other,你会在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也够用了). 现在是从DSServer.pas中贴出来的一小段代码,来说明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 服务,选择所有的通讯协议. 默认创建了一个叫做Project1.dproj的项目,并带有三个单元文 件,ServerContainerUnit1.pas,ServerMethodUnit1.pas和Unit1.pas.首先File.Save Project As保 存项目,并输入有实际意义的文件名称.将Unit1.pas保存为MainForm.pas,ServerMethodsUnit1.pas 保存为ServerMethodsUnitDemo.pas文件,保存Project1.dproj为DataSnapServer.dproj. 稍后我们将向项目组添加控制台应用程序和Window服务应用程序.首先我们来检查一下项目,并编 译工程.如果你编译DataSnapServer项目,将会出现一个错误信息(由于我们将 ServerMethodsUnit1.pas改名所致).错误原因是由于ServerContainerUnitDemo.pas单元中的 Implementation部分引用了ServerMethodsUnit1.pas单元.为了修复这个冲突,修改引用单元的文件 名称,从新编译.这是发现在第37行出现错误,使用了ServerMethodsUnit1中的TServerMethods1类型. 修改ServerMethodsUnit1为ServerMethodsUnitDemo.这时可以正确的编译项目了. ServerContainerUnitDemo的引用部分应该向下面代码所示: implementation uses Windows, ServerMethodsUnitDemo; {$R *.dfm} procedure TServerContainer1.DSServerClass1GetClass( DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass); begin PersistentClass := ServerMethodsUnitDemo.TServerMethods1; 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和Tag.AutoStart属性默认设置为 True,意味着在窗体创建后自动启动DataSnap服务.如果将AutoStart设置为False,需要手动调用 Start方法启动服务,并调用Stop方法停止服务.可以调用Started方法验证DataSnap服务是否已经 启动. HideAdmin属性默认设置为False.如果设置为True,连接到DataSnap服务的客户端将无法调用 Datasnap服务中的TDSAdmin类的内置方法.TDSAdmin不是一个真正的类,我们可以调用的TDSAdmin 方法定义在DSNames单元: TDSAdminMethods = class public const CreateServerClasses = 'DSAdmin.CreateServerClasses'; const CreateServerMethods = 'DSAdmin.CreateServerMethods'; const FindClasses = 'DSAdmin.FindClasses'; const FindMethods = 'DSAdmin.FindMethods'; const FindPackages = 'DSAdmin.FindPackages'; const GetPlatformName = 'DSAdmin.GetPlatformName'; const GetServerClasses = 'DSAdmin.GetServerClasses'; const GetServerMethods = 'DSAdmin.GetServerMethods'; const GetServerMethodParameters = 'DSAdmin.GetServerMethodParameters'; const DropServerClasses = 'DSAdmin.DropServerClasses'; const DropServerMethods = 'DSAdmin.DropServerMethods'; const GetDatabaseConnectionProperties = 'DSAdmin.GetDatabaseConnectionProperties'; 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 TServerContainer1.DSServer1Connect( DSConnectEventObject: TDSConnectEventObject); begin LogInfo('Connect ' + DSConnectEventObject.ChannelInfo.Info); end; 在OnTrace事件处理程序中我们可以使用TraceInfo.Message中的信息记录服务端正在做什么. function TServerContainer1.DSServer1Trace(TraceInfo: TDBXTraceInfo): CBRType; begin LogInfo('Trace ' + TraceInfo.CustomCategory); LogInfo(' ' + TraceInfo.Message); 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,"DataSnap.ServerMethod", "TServerMethods1.AS_GetRecords"]} 17:05:55.499 Prepare 如你所见,TraceInfo.Message中包括了传输信息的字节数和被调用的方法名称等信息. 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和OnPrepare.OnPrepare事件可用于准备服务方法.使用D2009或使用D2010手动向 容器中添加TDSServerClass时,OnGetClass事件必须由我们自己实现,以便于指定一个可远程调用 的类.在D2010的向导中,已经自动为我们实现了OnGetClass事件,如下: procedure TServerContainer1.DSServerClass1GetClass( DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass); begin PersistentClass := ServerMethodsUnitDemo.TServerMethods1; end; 注意:当我们重命名了自动生成的代码单元ServerMethodsUnit1为 ServerContainerUnitDemo.pas后必须修改这里. 2.1.1.1.3. TDSTCPSERVERTRANSPORT TDSTCPServerTransport组件负责在DataSnap服务端和客户端进行通讯,使用TCP/IP协议. TDSTCPServerTransport组件有五个重要的属性:BufferKBSize,Filters(D2010新特 性),MaxThreads,PoolSize,Port和Server. BufferKBSize属性指定通讯缓冲区大小,默认设置为32KB.Filters属性可以包含一个传输过滤 器集合,将在第四节讲解.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 TServerContainer1.DataModuleCreate(Sender: TObject); begin DSHTTPService1.Active := 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 TServerContainer1.DSHTTPService1Trace(Sender: TObject; AContext: TDSHTTPContext; ARequest: TDSHTTPRequest; AResponse: TDSHTTPResponse); begin LogInfo('HTTP Trace ' + AContext.ToString); LogInfo(' ' + ARequest.Document); LogInfo(' ' + AResponse.ResponseText); 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 TServerContainer1.DSHTTPServiceAuthenticationManager1HTTPAuthenticate( 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可以确保连接安全和数据包加密, 数据包被窃取也不会泄露用户和密码信息.可与你所在delphi app 域的ISP或Web管理员协商是否有可能使用HTTPS—--强烈推荐(如我使用的 是www.bobswart.hl). DataSnap服务应用程序另一个优势是可将HTTP验证信息(所用协议和上下文信息)记录下来.以 便于查谁登陆过,谁试图登录(如做欺骗登录操作试图获取DataSnap服务的权限). 2.1.1.2. SERVERMETHODSUNITDEMO 注意我们已经检查了ServerContainerUnitDemo.pas单元,现在看一下DataSnap服务的另外重要 的单元:ServerMethodsUnitDemo.pas单元.在新建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窗口应用程序,保存为DataSnapClient.dpr,主窗体 为ClientForm.pas.控件栏中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属性为DataSnap.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).保存单元为ServerMethodsClient.pas. 这个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 TForm2.Button1Click(Sender: TObject); var Server: TServerMethods1Client; begin Server := TServerMethods1Client.Create(SQLConnection1.DBXConnection); try ShowMessage(DateTimeToStr(Server.ServerTime)) finally Server.Free 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属性 为www.bobswart.nl.(注意不需要指定前缀,因为在CommunicationProtocol属性中指定了 通讯协议). 3. DATASNAP和数据库 除了使用Delphi2010 DataSnap框架创建简单的服务方法,我们还可以在服务端添加数据库操作, 实现多层数据库应用—DataSnap服务连接到数据库,但DataSnap客户端是瘦客户端,不含任何数据 库驱动. 我们同样可以快速创建数据库操作的DataSnap范例,只使用SQLConnection组件和已创建的客户 端类.也可使用另外两个DataSnap组件TsqlServerMethod和TDSProviderConnection. 首先,我们要确保服务端公布了一个数据集,打开ServerMethodsUnitDemo并在数据模块中添加 一个TSQLConnection组件.连接TSQLConnection组件到数据库和表(本例我连接到BlackFishSQL的 Employees表).将TSQLConnection组件添加到数据模块的设计区域.设置Driver属性为 BlachFishSql.然后设置Database属性为employee.jds文件的路径: C:\Documents and Settings\All Users\Documents\RAD Studio\7.0\Demos\database\databases\BlackfishSQL on Windows XP, or C:\Users\Public\Documents\ RAD Studio\7.0\Demos\database\databases\BlackfishSQL. 将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 TServerMethods1.GetEmployees: TDataSet; begin SQLDataSet1.Open; // 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方法,七个TServerMethods.As_XXX方法 (由原来的IAppServer提供),和最后我们自己的TServerMethods1的方法:EchoString,ServerTime, 和GetEmployees方法. 本例我们选择TServerMethods1.GetEmployees作为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),同时会将 TSQLConnection.Connected设置为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单元中发 布的名称). 现在我们可以设置TClientDataSet.Active为True,在设计时查看数据. 设置TClientDataSet.Active为True,同时将TSQLConnection.Connected置为True.注意在设计 时最好不要将这两个属性设置为True.首先,如果你在IDE中打开DataSnap客户端项目,将会试图连 接服务端,如果服务端没开启将失败.其次,如果在运行时启动应用程序,并且连接不可用,应用程 序将会抛出异常.从而应用程序不能使用本地数据,导致远程连接无效将不能使用应用程序. 最好的方式是使用菜单选项或按钮明确的设置TSQLConnection组件连接,TClientDataSet组件 进行获取数据.并提交包括username/password的信息,将在后面说明.现在确保 TClientDataSet.Active属性设置为False,同时TSQLConnection.Connected属性为False.在客户 端窗体中放一个按钮,在OnClick事件中明确的打开TClientDataSet. procedure TForm2.Button2Click(Sender: TObject); begin ClientDataSet1.Open; end; 现在添加代码提交数据变更,保存回服务器. 3.2.2 数据库更新 有两种方法将数据修改保存会服务端:自动和手动.都是调用一下方法,但是会自动调用或手动 调用,各有优缺点. 对于自动方式,我们可以使用TClientDataSet的数据修改时触发的OnAfterInsert,OnAfterPost 和OnAfterDelete事件.在事件处理程序中,实现很简单,调用TClientDataSet的ApplyUpdates方法, 发送变更,将Delta包发送到服务端保存回数据库. procedure TForm2.ClientDataSet1AfterPost(DataSet: TDataSet); begin ClientDataSet1.ApplyUpdates(0); end; 如果发生了更新错误,将会触发TClientDataSet的OnReconcileError事件,更多信息见3.2.3 手动方式发生更新也是使用TClientDataSet的ApplyUpdates方法.但是这时方法不在 OnAfterInsert,OnAfterPost和OnAfterDelete事件中执行.而是我们添加一个按钮让用户显示的提交更 新. procedure TForm2.btnUpdateClick(Sender: TObject); begin ClientDataSet1.ApplyUpdates(0); end; 自动提交的好处当然用户不会忘记将变更保存回服务端.然而,缺点是无法提供Undo能力.一旦 提交数据就更新回了服务器.另外,如果使用手动提交,所有变更保存在客户端---TClientDataSet 组件的内存中.这样就允许用户Undo部分变更:使特定记录或全部记录放弃更新.点击更新按钮显 示调用ApplyUpdate方法.可能会导致用户忘记提交修改数据.我们应该在窗口关闭时添加代码检 查TClientDataSet中是否还有未提交数据(检查TClientDataSet.ChangeCount属性). 3.2.3. RECONCILE ERRORS TClientDataSet.ApplyUpdates方法有一个参数:应用更新时允许发生的最大错误数量.如果有 两个客户端连接到了DataSnap服务端,获取Employees数据并同时修改了第一行数据.依据目前为 止我们的实现,两个客户端都会使用TClientDataSet的ApplyUpdates将数据变更到DataSnap服务 端.如果都将ApplyUpdates的参数MaxErrors设置为0,则第二个客户端的提交将会停止.第二个客 户端应该使用一个大于0的参数指定允许的错误/冲突数.然而,即使第二个客户端将MaxErrors设 置为-1(不管有多少错误发生都继续提交后面的更新记录),都不会提交被第一个用户更新过的记 录.换句话说,你需要执行一系列冲突处理来解决这些更新已经被更新的记录或列的冲突问题. 幸运的是,Delphi提供了一个很强大的对话框来处理这个问题.当在DataSnap客户端需要做一些 冲突处理时,都可以使用这个对话框(或自己实现,但最终都是处理冲突问题). 使用Delphi提供的功能,File.New.Other,在Delphi文件子目录中选择Reconcile Error对话框 图标. 选中这个图标点击OK,保存为RecError.pas,加入到DataSnapClient项目.这个单元包括了定义 和实现更新错误对话框.来解决数据库更新错误. ReconcileErrorForm窗体实例将按需要动态创建.那么如何使用这个特殊的ReconcileErrorForm窗体呢? 好,其实很简单.对于每个没有成功更新的记录,都会触发TClientDataSet的OnReconcileError事件.定义如 下: procedure TForm2.ClientDataSet1ReconcileError(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 TFrmClient.ClientDataSet1ReconcileError(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组件和dbxconnections.ini及dbxgrivers.ini文件(可在C:\Documents and Settings\All Users\ Documents\RAD Studio\dbExpress\7.0 directory on Windows XP or in the C:\Users\Public\Documents\RAD Studio\dbExpress\7.0中到). dbxdrivers.ini文件指定所用驱动, DriverPackageLoader及 MetaDataPackageLoader(通常指向同一个包).对于BlackFishSQL,使用的DBXClientDriver140.bpl文件,需要 部署到服务端.更多关于部署BlashFishSQL信息见RAD Studio\7.0 目录下的deploy_en.htm文件. 3.4. 重用已有的远程数据模块 如果你有一个远程数据模块类,也可以将其组合到新的DataSnap项目中来.但是必须要牺牲一些特性,尤其 是引入了COM. 首先,如果有一个你要迁移的DataSnap服务应用程序,而不仅仅是一个远程数据模块,你需要使用命令行 /unregister注销DataSnap服务.不做这步将无法注销远程数据模块.在远程数据模块单元,移除initialization区 域.如果还希望这个单元在D2007及一下版本重用,可以使用编译开关: {$IF CompilerVersion >= 20} initialization TComponentFactory.Create(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 TServerContainer1.DSServer1Connect( DSConnectEventObject: TDSConnectEventObject); var i: Integer; begin LogInfo('Connect ' + DSConnectEventObject.ChannelInfo.Info); for i:=0 to DSConnectEventObject.Transport.Filters.Count-1 do LogInfo(' Filter: ' + DSConnectEventObject.Transport.Filters.GetFilter(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 TLogFilter.SetParameterValue(const ParamName, ParamValue: UnicodeString): Boolean; begin Result := True; end; constructor TLogFilter.Create; begin inherited Create; end; destructor TLogFilter.Destroy; begin inherited Destroy; end; function TLogFilter.ProcessInput(const Data: TBytes): TBytes; begin Result := Data; // log incoming data end; function TLogFilter.ProcessOutput(const Data: TBytes): TBytes; begin Result := Data; // log outgoing data end; function TLogFilter.Id: UnicodeString; begin Result := LogFilterName; end; 最后,重要的实现部分是在initialization和finalization中注册DataSnap传输过滤器.确保客户端可以到这个传 输过滤器,并在请求时自动使用. initialization TTransportFilterFactory.RegisterFilter(LogFilterName, TLogFilter); finalization TTransportFilterFactory.UnregisterFilter(LogFilterName); end. 为了在DataSnap服务端使用这个传输过滤器,我们需要将其加入到TDSTCPServer或DSHTTPService组件 的Filters属性中,非常简单.在设计时,已知存在一个ZLibCompression过滤器,但无法感知新的过滤器(除非将其 添加到设计时包中并安装).幸运的是我们也可以在运行时添加过滤器,在ServerContainerUnitDemo单元中引用 过滤器单元,然后手动向Filters属性中添加过滤器.如. procedure TServerContainer1.DataModuleCreate(Sender: TObject); begin DSTCPServerTransport1.Filters.AddFilter(LogFilterName); DSHTTPService1.Filters.AddFilter(LogFilterName); DSHTTPService1.Active := True; end; 这将确保服务端使用LogFilter,客户端只要在单元中引用LogFilter单元就会自动使用LogFilter.否则将抛出 错误信息: 注意DataSnap客户端和服务端将获取各自的logfile实例.虽然使用的同一个过滤器,但是却不需用 ParamStr(0)来区分日志信息. 4.3. 加密过滤器 即使是一个如4.2中的一个简单的过滤器,也需要自己去扩展,很复杂.DataSnap提供的过滤器不太完整.事实 上,有大量的三方过滤器可以使用,有Daniele Teti开发的DataSnap Filters Compendium可以 在www.danieleteti.it/?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. 服务方法,部署,客户端 增加功能时只需要修改被两个项目共享的ServerMethodUnit1.pas单元.默认有一个范例函数,就是上面的 范例,我们包含多个方法(组件说明和源码见2.1.4.).服务端方法实现后,我们就可以将ISAPI DLL部署到IIS. 详细信息可见barcadero/jimtierney/2009/08/20/31502. 本例中如你没有Web服务可用于部署,可以使用我已部署的DataSnap ISAPI.注意我没有发布 TDataSetProvider,也没有实现返回数据的GetEmployees方法,但是ServerTime和EchoString方法都运行 良好,可以用来测试DataSnap客户端了. 在客户端连接ISAPI DataSnap服务端前,可以使用Data Explorer检查一下是否可以连接到ISAPI DataSnap服务. Data Explorer上有一个新的叫做DATASNAP的目录,展开后,第一个连接叫做 DataSnapConnection,右键修改连接.在这个对话框中我们可以选择协议,主机(如你没有自己的Web服务可 以使用www.bobswart.nl),端口号,以及ISAPI DataSnap服务应用程序在Web服务器上的URL路径,这里是 cgi-bin/DSISAPIServer.dll.点击测试连接. 点击OK关闭窗口,在Data Explorer,展开DATASNAPCONNECTION节点查看表,视图,过程,函数和 同义词.下图中,过程包括DSAdmin,DSMetaData,TServerMethods1.AS_XXX及我们自定义的三个函数 EchoString,ServerTime 和 GetEmployees. 不需要写DataSnap客户端,现在就可以测试这些方法.例如EchoString方法(发送什么返回什么).右键点击 TServerMethods1.GetEmployees方法,选择View Parameters,弹出一个新窗口,输入参数(例如42).在这个新 窗口中右击,选择”执行远程服务方法”.运行结果将显示在ReturnValue中. 这样我们就可以调用远程DataSnap服务方法.为了在客户端连接到远程服务端,我们只需要修改 TSQLConnection组件的属性.原来我们连接都Windows版本的DataSnap服务,现在我们需要修改设置连接到 Web版本. 注意,如果你使用的是我发布的DSISAPIServer.dll,我已经禁用了TDataSetProvider,而且GetEmployees方法 不返回任何数据.但你可以使用ServerTime 和 EchoString方法. 6.如何使用REST和JSON DataSnap2010支持REST和JSON.DataSnap2010特性REST支持DataSnap HTTP请求.例如,如果 DataSnap服务的URI是www.bobswart.nl/cgi-bin/DSISAPIServer.dll.我们可以在此URL后 加 /datasnap/rest,后跟服务类名称,方法名称和参数.语法如下: server/datasnap/rest/<class>/<method>/<parameters> 对于我的服务器上的TServerMethod1模块中的ServerTime方法,URL如下: www.bobswart.nl/cgi-bin/DSISAPIServer.dll/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 TCallbackClient.Execute(const Arg: TJSONValue): TJSONValue; var Data: TJSONValue; begin Data := TJSONValue(Arg.Clone); ShowMessage('Callback: ' + TJSONObject(Data).Get(0).JSonValue.value); Result := Data end; 例如, 在方法实际返回前(如方法正在执行),回调函数将显示EchoString方法传递参数的值.服务端新的 EchoString方法实现需要将String值赋给一个TJSONObject对象,并将其传递给回调函数.如下: function TServerMethods2.EchoString(Value: string; callback: TDBXcallback): string; var msg: TJSONObject; pair: TJSONPair; begin Result := Value; msg := TJSONObject.Create; pair := TJSONPair.Create('ECHO', Value); pair.Owned := True; msg.AddPair(pair); callback.Execute(msg); end; 注意这个回调函数将在客户端执行—然后在服务端Echostring方法执行完毕前返回 最后,在客户端调用EchoString方法也需要修改,因为我们现在要提供一个回调类TCallbackClient的实例,如 下所示: var MyCallback: TCallbackClient; begin MyCallback := TCallbackClient.Create; try Server., MyCallback); finally MyCallback.Free; 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应用程序中调用这些方法.最后一个范 例,File.New Project启动Delphi Prism新项目向导.选择项目类型. 在Windows 项目类型中选择Windows Application,修改WindowsApplication1为DataSnapClient. 点击OK按钮,创建一个带有Main.pas单元的新项目. 在Server Explorer,选择新建的DataSnap服务连接,属性框中到ConnectionString,如下: communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;ds authenticationpassword=Swart 右击数据连接节点,选择生成客户端代理(Generate Client Proxy)选项.生成新文件ClientProxy1.pas,其中 定义了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: System.Data.IDataReader; 除了代理类还在项目的引用节点中添加了Borland.Data.AdoDbxClient 和Borland.Data.DbxClientDriver 引用. 从TServerMethods1Client类代码片段中可见,类有两个构造函数:有使用了一个ADBXConnection参数, 第二个构造函数还有一个AInstanceOwner的Boolean类型参数.这意味着我们必须使用参数调用构造函数. 为了支持这个功能,必须修改项目属性设置.在解决方案管理器中右击DataSnapClient,选择属性.如下图,点 击Compatibility标签,选中”Allow Create constructor calls”,将允许我们调用.Create构造方法,传递参数,而不 仅仅是使用new关键字. 现在回到主窗体,添加一个按钮.在Click事件中创建一个DataSnap服务连接并调用方法. method MainForm.button1_Click(sender: System.Object; e: System.EventArgs); var Client: ClientProxy1.TServerMethods1Client; Connection: Borland.Data.TAdoDbxDatasnapConnection; begin Connection := new Borland.Data.TAdoDbxDatasnapConnection(); Connection.ConnectionString := 'communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;dsauth enticationpassword=Swart'; Connection.Open; try Client := ClientProxy1.TServerMethods1Client.Create(Connection); MessageBox.Show( Client.EchoString('Delphi Prism 2010')); finally Connection.Close; end; end; 运行结果如下图所示: 同样方式,我们调用GetEmployees方法获取结果集并显示到DataGridView.这里有个小问题, DataGridView方法返回的是IDataReader(等价于TSQLDataSet结果集),而不是DataSet和DataTable.我们 必须写几行代码将GetEmployees返回的结果集保存到DataSet的DataTabl中(等价于Win32中的 TClientDataSet). method MainForm.button1_Click(sender: System.Object; e: System.EventArgs); var Client: ClientProxy1.TServerMethods1Client; Connection: Borland.Data.TAdoDbxDatasnapConnection; Employees: System.Data.IDataReader; ds: System.Data.DataSet; dt: System.Data.DataTable; begin Connection := new Borland.Data.TAdoDbxDatasnapConnection(); Connection.ConnectionString := 'communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;dsauthenticationpassword=Swart'; Connection.Open; try Client := ClientProxy1.TServerMethods1Client.Create(Connection); Employees := Client.GetEmployees; ds := new DataSet(); dt := new DataTable("DataSnap"); ds.Tables.Add(dt); ds.Load(Employees, LoadOPtion.PreserveChanges, ds.Tables[0]); dataGridView1.DataSource := ds.Tables[0]; MessageBox.Show( Client.EchoString('Delphi Prism 2010')); finally Connection.Close; 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的employee.jds数据库.见
[ C:\Documents and Settings\All Users\Documents\RAD
Studio\7.0\Demos\database\databases\BlackfishSQL\employee.jds].在截图中可以看到我使用的
是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,点击File.New.Other,你会在Object Repository中看到DataSnap服务向导中
显示的三个图标:DataSnap Server,DataSnap WebBroker Server,和Server Module.
双击第一个(后面的两个在下面的小结中讲解),弹出如下对话框:
界面中第一部分是控制项目类型的.默认可以生成可视化的带有主窗体的VCL窗体应用程序.第二个
选项是创建控制台应用程序,生成一个控制台窗口—可以用来输出请求应答信息(用Writeln语句输出
服务应用程序正在做什么).这两种方式都是为了做范例或最初部署,很少用于最终部署.由于
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论