this chapter describes how to create a multi-tiered, client/server database application. A multi-tiered client/server application is partitioned into logical units which run in conjunction on separate machines. Multi-tiered applications share data and communicate with one another over a local-area network or even over the Internet. They provide many benefits, such as centralized business logic and thin client applications.
In its simplest form, sometimes called the "three-tiered model," a multi-tiered application is partitioned into thirds:
In this three-tiered model, the application server manages the flow of data between clients and the remote database server, so it is sometimes called a "data broker." With Delphi you usually only create the application server and its clients, although, if you are really ambitious, you could create your own database back end as well.
In more complex multi-tiered applications, additional services reside between a client and a remote database server. For example, there might be a security services broker to handle secure Internet transactions, or bridge services to handle sharing of data with databases on platforms not directly supported by Delphi.
Delphi support for multi-tiered applications is based on the Multi-tier Distributed Application Services Suite (MIDAS). This chapter focuses on creating a three-tiered database application using the MIDAS technology. Once you understand how to create and manage a three-tiered application, you can create and add additional service layers based on your needs.
The multi-tiered database model breaks a database application into logical pieces. The client application can focus on data display and user interactions. Ideally, it knows nothing about how the data is stored or maintained. The application server (middle tier) coordinates and processes requests and updates from multiple clients. It handles all the details of defining datasets and interacting with the remote database server.
The advantages of this multi-tiered model include the following:
MIDAS provides the mechanism by which client applications and application servers communicate database information. Using MIDAS requires MIDAS.DLL, which is used by both client and server applications to manage datasets stored as data packets. Building MIDAS applications may also require the SQL explorer to help in database administration and to import server constraints into the Data Dictionary so that they can be checked at any level of the multi-tiered application.
Note: You must purchase server licenses for deploying your MIDAS applications.
MIDAS-based multi-tiered applications use the components on the MIDAS page of the component palette, plus a remote data module that is created by a wizard on the Multitier page of the New Items dialog. These components are described in Table 14.1:
The following numbered steps illustrate a normal sequence of events for a MIDAS-based multi-tiered application:
To the end user, the client application of a multi-tiered application looks and behaves no differently than a traditional two-tiered application that uses cached updates. Structurally, the client application looks a lot like a flat-file single-tiered application. User interaction takes place through standard data-aware controls that display data from a client dataset component. For detailed information about using the properties, events, and methods of client datasets, see "Creating and using a client dataset."
Unlike in a flat-file application, the client dataset in a multi-tiered application obtains its data through the IAppServer interface on the application server. It uses this interface to post updates to the application server as well. For more information about the IAppServer interface, see "Using the IAppServer interface". The client gets this interface from a connection component.
The connection component establishes the connection to the application server. Different connection components are available for using different communications protocols. These connection components are summarized in the following table:
Note: Two other connection components, TRemoteServer and TMIDASConnection, are provided for backward compatibility.
For more information about using connection components, see "Connecting to the application server".
The application server includes a remote data module that provides an IAppServer interface, which client applications use to communicate with data providers. There are three types of remote data modules:
As with any data module, you can include any nonvisual component in the remote data module. In addition, the remote data module includes a dataset provider component for each dataset the application server makes available to client applications. A dataset provider
Often, the provider uses BDE- or ADO-enabled datasets such as you find in a two-tiered application. You can add database and session components as needed, just as in a BDE-based two-tiered application, or ADO connection components as in an ADO-based two-tiered application.
Note: Do not confuse the ADO connection component, which is analogous to a database component in a BDE-based application, with the connection components used by client applications in a multitiered application.
For more information about two-tiered applications, see "Building one- and two-tiered applications".
If the application server is MTS-enabled, the MTS data module includes events for when the application server is activated or deactivated. This permits the application server to acquire database connections when activated and release them when deactivated.
Using MTS lets your remote data module take advantage of
Using just-in-time activation and as-soon-as-possible deactivation provides a middle ground between routing all clients through a single remote data module instance, and creating a separate instance for every client connection. With a single remote data module instance, the application server must handle all database calls through a single database connection. This acts as a bottleneck, and can impact performance when there are many clients. With multiple instances of the remote data module, each instance can maintain a separate database connection, thereby avoiding the need to serialize database access. However, this monopolizes resources because other clients can't use the database connection while it is associated with another client's remote data module.
To take advantage of transactions, just-in-time activation, and as-soon-as-possible deactivation, remote data module instances must be stateless. This means you must provide additional support if your client relies on state information. For example, the client must pass information about the current record when performing incremental fetches. For more information about state information and remote data modules in multi-tiered applications, see "Supporting state information in remote data modules".
By default, all automatically generated calls to an MTS data module are transactional (that is, they assume that when the call exits, the MTS data module can be deactivated and any current transactions can be committed or rolled back). You can write an MTS data module that depends on persistent state information by setting the AutoComplete property to False, but it will not support transactions, just-in-time activation, or as-soon-as-possible deactivation.
Warning: When using MTS, database connections should not be opened until the remote data module is activated. While developing your application, be sure that all datasets are not active and the database is not connected before running your application. In the application itself, you must add code to open database connections when the data module is activated and to close them when the data module is deactivated.
Object pooling allows you some of the benefits available from MTS when you are not using DCOM. Under object pooling, you can limit the number of instances of your remote data module that are created. This limits the number of database connections that you must hold, as well as any other resources used by the remote data module.
When the server receives client requests, it passes them on to the first available remote data module in the pool. If there is no available remote data module, it creates a new one (up to a maximum number that you specify). This provides a middle ground between routing all clients through a single remote data module instance (which can act as a bottleneck), and creating a separate instance for every client connection (which can consume many resources).
If a remote data module instance in the pool does not receive any client requests for a while, it is automatically freed. This prevents the pool from monopolizing resources unless they are used.
Because a single instance of a remote data module potentially handles requests from several clients, it must not rely on persistent state information. See "Supporting state information in remote data modules" for more information on how to ensure that your remote data module is stateless.
To take advantage of object pooling, your remote data module must override the UpdateRegistry method. In the overridden method, you can call RegisterPooled when the remote data module registers and UnregisterPooled when the remote data module unregisters.
You can only take advantage of object pooling when the connection is formed using HTTP.
Remote data modules on the application server support the IAppServer interface. Connection components on client applications look for this interface to form connections.
IAppServer provides the bridge between client applications and the provider components in the application server. Most client applications do not use IAppServer directly, but invoke it indirectly through the properties and methods of the client dataset. However, when necessary, you can make direct calls to the IAppServer interface by using the AppServer property of the client dataset.
Table 14.3 lists the methods of the IAppServer interface, as well as the corresponding methods and events on the provider component and the client dataset. These IAppServer methods include a Provider parameter to indicate which provider on the application server should provide data or resolve updates. In addition, most methods include an OleVariant parameter called OwnerData that allows the client application and application server to pass custom information back and forth. OwnerData is not used by default, but is passed to all event handlers so that you can write code that allows your application server to adjust for this information before and after each client call.
Each communications protocol you can use to connect your client applications to the application server provides its own unique benefits. Before choosing a protocol, consider how many clients you expect, how you are deploying your application, and future development plans.
DCOM provides the most direct approach to communication, requiring no additional runtime applications on the server. However, because DCOM is not included with Windows 95, client machines may not have DCOM installed.
DCOM provides the only approach that lets you use MTS security. MTS security is based on assigning roles to the callers of MTS objects. When calling into MTS using DCOM, DCOM informs MTS about the client application that generated the call. MTS can then accurately determine the role of the caller. When using other protocols, however, there is a runtime executable, separate from the application server, that receives client calls. This runtime executable makes COM calls into the application server on behalf of the client. MTS can't assign roles to separate clients because, as far as MTS can tell, all calls to the application server are made by the runtime executable. For more information about MTS security, see "Role-based security".
TCP/IP Sockets let you create lightweight clients. For example, if you are writing a Web-based client application, you can't be sure that client systems support DCOM. Sockets provide a lowest common denominator that you know will be available for connecting to the application server. For more information about Sockets, see "Working with sockets".
Instead of instantiating the remote data module directly from the client (as happens with DCOM), sockets use a separate application on the server (ScktSrvr.exe), which accepts client requests and instantiates the remote data module using COM. The connection component on the client and ScktSrvr.exe on the server are responsible for marshaling IAppServer calls.
Note: ScktSrvr.exe can run as an NT service application. Register it with the Service manager by starting it using the -install command line option. You can unregister it using the -uninstall command line option.
Before you can use a socket connection, the application server must register its availability to clients using a socket connection. By default, all new remote data modules automatically register themselves by adding a call to EnableSocketTransport in the UpdateRegistry method. You can remove this call to prevent socket connections to your application server.
Note: Because older servers did not add this registration, you can disable the check for whether an application server is registered by unchecking the Connections|Registered Objects Only menu item on ScktSrvr.exe.
When using sockets, there is no protection on the server against client systems failing before they release a reference to interfaces on the application server. While this results in less message traffic than when using DCOM (which sends periodic keep-alive messages), this can result in an application server that can't release its resources because it is unaware that the client has gone away.
HTTP lets you create clients that can communicate with an application server that is protected by a "firewall". HTTP messages provide controlled access to internal applications so that you can distribute your client applications safely and widely. Like Socket connections, HTTP messages provide a lowest common denominator that you know will be available for connecting to the application server. For more information about HTTP messages, see "Creating Internet server applications".
Instead of instantiating the remote data module directly from the client (as happens with DCOM), HTTP-based connections use a Web server application on the server (httpsrvr.dll) which accepts client requests and instantiates the remote data module using COM. Because of this, they are also called Web connections. The connection component on the client and httpsrvr.dll on the server are responsible for marshaling IAppServer calls.
Web connections can take advantage of the SSL security provided by wininet.dll (a library of internet utilities that runs on the client system). Once you have configured the Web server on the server system to require authentication, you can specify the user name and password using the properties of the Web connection component.
As an additional security measure, the application server must register its availability to clients using a Web connection. By default, all new remote data modules automatically register themselves by adding a call to EnableWebTransport in the UpdateRegistry method. You can remove this call to prevent Web connections to your application server.
Web connections can take advantage of object pooling. This allows your server to create a limited pool of remote data module instances that are available for client requests. By pooling the remote data modules, your server does not consume the resources for the data module and its database connection except when they are needed. For more information on object pooling, see "Pooling remote data modules".
Unlike other connection components, you can't use callbacks when the connection is formed via HTTP.
OLEnterprise lets you use the Business Object Broker instead of relying on client-side brokering. The Business Object Broker provides load-balancing, fail-over, and location transparency.
When using OLEnterprise, you must install OLEnterprise runtime on both client and server systems. OLEnterprise runtime handles the marshalling of Automation calls and communicates between the client and server system using remote procedure calls (RPCs). For more information, see the OLEnterprise documentation.
CORBA lets you integrate your multi-tiered database applications into an environment that is standardized on CORBA. For example, the MIDAS client for Java components rely on a CORBA connection. Because CORBA (and Java) is available on multiple platforms, this allows you to write cross-platform MIDAS applications. For more information about using CORBA in Delphi, see "Writing CORBA applications".
By using CORBA, your application automatically gets the benefits of load-balancing, location transparency, and fail-over from the ORB runtime software. In addition, you can add hooks to take advantage of other CORBA services.
The general steps for creating a multi-tiered database application are
The order of creation is important. You should create and run the application server before you create a client. At design time, you can then connect to the application server to test your client. You can, of course, create a client without specifying the application server at design time, and only supply the server name at runtime. However, doing so prevents you from seeing if your application works as expected when you code at design time, and you will not be able to choose servers and providers using the Object Inspector.
Note: If you are not creating the client application on the same system as the server, and you are not using a Web connection or socket connection, you may want to register or install the application server on the client system. This makes the connection component aware of the application server at design time so that you can choose server names and provider names from a drop-down list in the Object Inspector. (If you are using a Web connection or socket connection, the connection component fetches the names of registered servers from the server machine.)
You create an application server very much as you create most database applications. The major difference is that the application server includes a dataset provider.
To create an application server, start a new project, save it, and follow these steps:
For more detailed information about setting up a remote data module, see "Setting up the remote data module".
Note: Remote data modules are more than simple data modules. The CORBA remote data module acts as a CORBA server. Other data modules are COM Automation objects.
When you set up and run an application server, it does not establish any connection with client applications. Instead, connection is maintained by client applications. The client application uses its connection component to establish a connection to the application server, which it uses to communicate with its selected provider. All of this happens automatically, without your having to write code to manage incoming requests or supply interfaces.
When you create the remote data module, you must provide certain information that indicates how it responds to client requests. This information varies, depending on the type of remote data module. See "The structure of the application server" for information on what type of remote data module you need.
To add a TRemoteDataModule component to your application, choose File|New and select Remote Data Module from the Multitier page of the new items dialog. You will see the Remote Data Module wizard.
You must supply a class name for your remote data module. This is the base name of a descendant of TRemoteDataModule that your application creates. It is also the base name of the interface for that class. For example, if you specify the class name MyDataServer, the wizard creates a new unit declaring TMyDataServer, a descendant of TRemoteDataModule, which implements IMyDataServer, a descendant of IAppServer.
Note: You can add your own properties and methods to the new interface. For more information, see "Extending the application server's interface".
If you are creating a DLL (Active Library), you must specify the threading model in the Remote Data Module wizard. You can choose Single-threaded, Apartment-threaded, Free-threaded, or Both.
If you are creating an EXE, you must specify what type of instancing to use. You can choose Single instance or Multiple instance (Internal instancing applies only if the client code is part of the same process space.)
To add a TMTSDataModule component to your application, choose File|New and select MTS Data Module from the Multitier page of the new items dialog. You will see the MTS Data Module wizard.
You must supply a class name for your remote data module. This is the base name of a descendant of TMTSDataModule that your application creates. It is also the base name of the interface for that class. For example, if you specify the class name MyDataServer, the wizard creates a new unit declaring TMyDataServer, a descendant of TMTSDataModule, which implements IMyDataServer, a descendant of IAppServer.
Note: You can add your own properties and methods to your new interface. For more information, see "Extending the application server's interface".
MTS applications are always DLLs (Active Libraries). You must specify the threading model in the MTS Data Module wizard. Choose Single, Apartment, Free, or Both.
Note: The Apartment and Free models under MTS are different than the corresponding models under DCOM.
You must also specify the MTS transaction attributes of your remote data module. You can choose from the following options:
To add a TCorbaDataModule component to your application, choose File|New and select CORBA Data Module from the Multitier page of the new items dialog. You will see the CORBA Data Module wizard.
You must supply a class name for your remote data module. This is the base name of a descendant of TCorbaDataModule that your application creates. It is also the base name of the interface for that class. For example, if you specify the class name MyDataServer, the wizard creates a new unit declaring TMyDataServer, a descendant of TCorbaDataModule, which implements IMyDataServer, a descendant of IAppServer.
Note: You can add your own properties and methods to your new interface. For more information on adding to your data module's interface, see "Extending the application server's interface".
The CORBA Data Module wizard lets you specify how you want your server application to create instances of the remote data module. You can choose either shared or instance-per-client.
Note: Unlike instancing for COM servers, where the model determines the number of instances of the process that run, with CORBA, instancing determines the number of instances created of your object. They are all created within a single instance of the server executable.
In addition to the instancing model, you must specify the threading model in the CORBA Data Module wizard. You can choose Single- or Multi-threaded.
Each remote data module on an application server contains one or more provider components. Each client dataset uses a specific provider, which acts as the bridge between the client dataset and the data it represents. A provider component (TDataSetProvider) takes care of packaging data into data packets that it sends to clients and applying updates received from the client.
Most of the data logic in the application server is handled by the provider components contained in the remote data module. Event handlers that respond to client requests implement your business and data logic, while properties on the provider component control what information is included in data packets. See "Using provider components" for details on how to use a provider component to control the interaction with client applications.
Client applications interact with the application server by creating or connecting to an instance of the remote data module. They use its interface as the basis of all communication with the application server.
You can add to your remote data module's interface to provide additional support for your client applications. This interface is a descendant of IAppServer and is created for you automatically by the wizard when you create the remote data module.
To add to the remote data module's interface, you can
What Delphi does when you add new entries to the interface depends on whether you are creating a COM-based (TRemoteDataModule or TMTSDataModule) or CORBA (TCorbaDataModule) server.
Note: You must explicitly save the TLB file by choosing Refresh in the type library editor and then saving the changes from the IDE.
Once you have added to your remote data module's interface, locate the properties and methods that were added to your remote data module's implementation. Add code to finish this implementation.
Client applications call your interface extensions using the AppServer property of their connection component. For more information on how to do this, see "Calling server interfaces".
You can allow the application server to call your client application by introducing a callback. To do this, the client application passes an interface to one of the application server's methods, and the application server later calls this method as needed. However, if your extensions to the remote data module's interface include callbacks, you can't use an HTTP-based connection. TWebConnection does not support callbacks. If you are using a socket-based connection, client applications must indicate whether they are using callbacks by setting the SupportCallbacks property. All other types of connection automatically support callbacks.
When using transactions or just-in-time activation under MTS, you must be sure all new methods call SetComplete to tell MTS when it is finished. This allows transactions to complete and so permits the remote data module to be deactivated. Furthermore, you can't return any values from your new methods that allow the client to communicate directly with objects or interfaces on the application server. This is because any communication that does not go through the remote data module's interface bypasses the MTS proxy, which can invalidate transactions. If you are using a stateless MTS data module, bypassing the MTS proxy can lead to crashes because you can't guarantee that the remote data module is active.
In most regards, creating a multi-tiered client application is similar to creating a traditional two-tiered client. The major differences are that a multi-tiered client uses
To create a multi-tiered client application, start a new project and follow these steps:
To establish and maintain a connection to an application server, a client application uses one or more connection components. You can find these components on the MIDAS page of the Component palette.
If you are not using CORBA, identify the server using the ServerName or ServerGUID property. ServerName identifies the base name of the class you specify when creating the remote data module on the application server. See "Setting up the remote data module" for details on how this value is specified on the server. If the server is registered or installed on the client machine, or if the connection component is connected to the server machine, you can set the ServerName property at design time by choosing from a drop-down list in the Object Inspector. ServerGUID specifies the GUID of the remote data module's interface. You can look up this value using the type library editor.
If you are using CORBA, identify the server using the RepositoryID property. RepositoryID specifies the Repository ID of the application server's factory interface, which appears as the third argument in the call to TCorbaVCLComponentFactory.Create that is automatically added to the initialization section of the CORBA server's implementation unit. You can also set this property to the base name of the CORBA data module's interface (the same string as the ServerName property for other connection components), and it is automatically converted into the appropriate Repository ID for you.
Usually the application server is on a different machine from the client application, but even if the server resides on the same machine as the client application (for example, during the building and testing of the entire multi-tier application), you can still use the connection component to identify the application server by name, specify a server machine, and use the application server interface.
When using DCOM to communicate with the application server, client applications include a TDCOMConnection component for connecting to the application server. TDCOMConnection uses the ComputerName property to identify the machine on which the server resides.
When ComputerName is blank, the DCOM connection component assumes that the application server resides on the client machine or that the application server has a system registry entry. If you do not provide a system registry entry for the application server on the client when using DCOM, and the server resides on a different machine from the client, you must supply ComputerName.
Note: Even when there is a system registry entry for the application server, you can specify ComputerName to override this entry. This can be especially useful during development, testing, and debugging.
If you have multiple servers that your client application can choose from, you can use the ObjectBroker property instead of specifying a value for ComputerName. For more information, see "Brokering connections".
If you supply the name of a host computer or server that cannot be found, the DCOM connection component raises an exception when you try to open the connection.
You can establish a connection to the application server using sockets from any machine that has a TCP/IP address. This method has the advantage of being applicable to more machines, but does not provide for using any security protocols. When using sockets, include a TSocketConnection component for connecting to the application server.
TSocketConnection identifies the server machine using the IP Address or host name of the server system, and the port number of the socket dispatcher program (Scktsrvr.exe) that is running on the server machine. For more information about IP addresses and port values, see "Describing sockets".
Three properties of TSocketConnection specify this information:
Address and Host are mutually exclusive. Setting one unsets the value of the other. For information on which one to use, see "Describing the host".
If you have multiple servers that your client application can choose from, you can use the ObjectBroker property instead of specifying a value for Address or Host. For more information, see "Brokering connections".
By default, the value of Port is 211, which is the default port number of the socket dispatcher programs supplied with Delphi. If the socket dispatcher has been configured to use a different port, set the Port property to match that value.
Note: You can configure the port of the socket dispatcher while it is running by right-clicking the Borland Socket Server tray icon and choosing Properties.
Although socket connections do not provide for using security protocols, you can customize the socket connection to add your own encryption. To do this, create and register a COM object that supports the IDataIntercept interface. This is an interface for encrypting and decrypting data. Next, set the InterceptGUID property of the socket connection component to the GUID for this COM object. Finally, right click the Borland Socket Server tray icon, choose Properties, and on the properties tab set the Intercept GUID to the same GUID. This mechanism can also be used for data compression and decompression.
You can establish a connection to the application server using HTTP from any machine that has a TCP/IP address. Unlike sockets, however, HTTP allows you to take advantage of SSL security and to communicate with a server that is protected behind a firewall. When using HTTP, include a TWebConnection component for connecting to the application server.
The Web connection component establishes a connection to the Web server application (httpsrvr.dll), which in turn communicates with the application server. TWebConnection locates httpsrvr.dll using a Uniform Resource Locator (URL). The URL specifies the protocol (http or, if you are using SSL security, https), the host name for the machine that runs the Web server and httpsrvr.dll, and the path to the Web server application (Httpsrvr.dll). Specify this value using the URL property.
Note: When using TWebConnection, wininet.dll must be installed on the client machine. If you have IE3 or higher installed, wininet.dll can be found in the Windows system directory.
If the Web server requires authentication, or if you are using a proxy server that requires authentication, you must set the values of the UserName and Password properties so that the connection component can log on.
If you have multiple servers that your client application can choose from, you can use the ObjectBroker property instead of specifying a value for URL. For more information, see "Brokering connections".
When using OLEnterprise to communicate with the application server, client applications should include a TOLEnterpriseConnection component for connecting to the application server. When using OLEnterprise, you can either connect directly to the server machine, or you can use the Business Object Broker.
ComputerName and BrokerName are mutually exclusive. Setting the value of one unsets the value of the other.
For more information about using OLEnterprise, see the OLEnterprise documentation.
Only the RepositoryID property is necessary in order to specify a CORBA connection. This is because a Smart Agent on the local network automatically locates an available server for your CORBA client.
However, you can limit the possible servers to which your client application connects by the other properties of the CORBA connection component. If you want to specify a particular server machine, rather than letting the CORBA Smart Agent locate any available server, use the HostName property. If there is more than one object instance that implements your server interface, you can specify which object you want to use by setting the ObjectName property.
The TCorbaConnection component obtains an interface to the CORBA data module on the application server in one of two ways:
For more information on early vs. late binding, see "Calling server interfaces".
If you have multiple servers that your client application can choose from, you can use an Object Broker to locate an available server system. The object broker maintains a list of servers from which the connection component can choose. When the connection component needs to connect to an application server, it asks the Object Broker for a computer name (or IP address, host name, or URL). The broker supplies a name, and the connection component forms a connection. If the supplied name does not work (for example, if the server is down), the broker supplies another name, and so on, until a connection is formed.
Once the connection component has formed a connection with a name supplied by the broker, it saves that name as the value of the appropriate property (ComputerName, Address, Host, RemoteHost, or URL). If the connection component closes the connection later, and then needs to reopen the connection, it tries using this property value, and only requests a new name from the broker if the connection fails.
Use an Object Broker by specifying the ObjectBroker property of your connection component. When the ObjectBroker property is set, the connection component does not save the value of ComputerName, Address, Host, RemoteHost, or URL to disk.
Note: Do not use the ObjectBroker property with OLEnterprise connections or CORBA connections. Both of these protocols have their own brokering services.
The main purpose of connection components is to locate and connect to the application server. Because they manage server connections, you can also use connection components to call the methods of the application server's interface.
To locate and connect to the application server, you must first set the properties of the connection component to identify the application server. This process is described in "Connecting to the application server". In addition, before opening the connection, any client datasets that use the connection component to communicate with the application server should indicate this by setting their RemoteServer property to specify the connection component.
The connection is opened automatically when client datasets try to access the application server. For example, setting the Active property of the client dataset to True opens the connection, as long as the RemoteServer property has been set.
If you do not link any client datasets to the connection component, you can open the connection by setting the Connected property of the connection component to True.
Before a connection component establishes a connection to an application server, it generates a BeforeConnect event. You can perform any special actions prior to connecting in a BeforeConnect handler that you code. After establishing a connection, the connection component generates an AfterConnect event for any special actions.
A connection component drops a connection to the application server when you
Note: Instead of using a single connection component to switch among available application servers, a client application can instead have more than one connection component, each of which is connected to a different application server.
Before a connection component drops a connection, it automatically calls its BeforeDisconnect event handler, if one is provided. To perform any special actions prior to disconnecting, write a BeforeDisconnect handler. Similarly, after dropping the connection, the AfterDisconnect event handler is called. If you want to perform any special actions after disconnecting, write an AfterDisconnect handler.
Applications do not need to call the IAppServer interface directly because the appropriate calls are made automatically when you use the properties and methods of the client dataset. However, while it is not necessary to work directly with the IAppServer interface, you may have added your own extensions to the remote data module's interface. When you extend the application server's interface, you need a way to call those extensions using the connection created by your connection component. You can do this using the AppServer property of the connection component. For more information about extending the application server's interface, see "Extending the application server's interface".
AppServer is a Variant that represents the application server's interface. You can call an interface method using AppServer by writing a statement such as
MyConnection.AppServer.SpecialMethod(x,y);
However, this technique provides late (dynamic) binding of the interface call. That is, the SpecialMethod procedure call is not bound until runtime when the call is executed. Late binding is very flexible, but by using it you lose many benefits such as code insight and type checking. In addition, late binding is slower than early binding, because the compiler generates additional calls to the server to set up interface calls before they are invoked.
When you are using DCOM or CORBA as a communications protocol, you can use early binding of AppServer calls. Use the as operator to cast the AppServer variable to the IAppServer descendant you created when you created the remote data module. For example:
with MyConnection.AppServer as IMyAppServer do SpecialMethod(x,y);
To use early binding under DCOM, the server's type library must be registered on the client machine. You can use TRegsvr.exe, which ships with Delphi to register the type library.
Note: See the TRegSvr demo (which provides the source for TRegsvr.exe) for an example of how to register the type library programmatically.
To use early binding with CORBA, you must add the _TLB unit that is generated by the type library editor to your project. To do this, add this unit to the uses clause of your unit.
When you are using TCP/IP or OLEnterprise, you can't use true early binding, but because the remote data module uses a dual interface, you can use the application server's dispinterface to improve performance over simple late-binding. The dispinterface has the same name as the remote data module's interface, with the string 'Disp' appended. You can assign the AppServer property to a variable of this type to obtain the dispinterface. Thus:
varTempInterface: IMyAppServerDisp;beginTempInterface := MyConnection.AppServer;...TempInterface.SpecialMethod(x,y);...end;
Note: To use the dispinterface, you must add the _TLB unit that is generated when you save the type library to the uses clause of your client module.
When client applications apply updates to the application server, the provider component automatically wraps the process of applying updates and resolving errors in a transaction. This transaction is committed if the number of problem records does not exceed the MaxErrors value specified as an argument to the ApplyUpdates method. Otherwise, it is rolled back.
In addition, you can add transaction support to your server application by adding a database component or using passthrough SQL. This works the same way that you would manage transactions in a two-tiered application. For more information about this sort of transaction control, see "Using transactions" and "Working with (connection) transactions."
If you are using MTS, you can broaden your transaction support by using MTS transactions. MTS transactions can include any of the business logic on your application server, not just the database access. In addition, because they support two-phase commits, MTS transactions can span multiple databases.
Warning: Two-phase commit is fully implemented only on Oracle7 and MS-SQL databases. If your transaction involves multiple databases, and some of them are remote servers other than Oracle7 or MS-SQL, your transaction runs a small risk of only partially succeeding. Within any one database, however, you will always have transaction support.
To use MTS transactions, extend the application server's interface to include method calls that encapsulate the transaction, if necessary. When configuring the MTS remote data module indicate that it must participate in transactions. When a client calls a method on your application server's interface, it is automatically wrapped in a transaction. All client calls to your application server are then enlisted in that transaction until you indicate that the transaction is complete. These calls either succeed as a whole or are rolled back.
Note: Do not combine MTS transactions with explicit transactions created by a database component or using passthrough SQL. When your remote data module is enlisted in an MTS transaction, it automatically enlists all of your database calls in the transaction as well.
For more information about using MTS transactions, see "MTS transaction support".
You can create master/detail relationships between client datasets in your client application in the same way you set up master/detail forms in one- and two-tiered applications. For more information about setting up master/detail forms, see "Creating master/detail forms".
However, this approach has two major drawbacks:
In multi-tiered applications, you can avoid these problems by using nested tables to represent the master/detail relationship. To do this, set up a master/detail relationship between the tables on the application server. Then set the DataSet property of your provider component to the master table.
When clients call the GetRecords method of the provider, it automatically includes the detail datasets as a DataSet field in the records of the data packet. When clients call the ApplyUpdates method of the provider, it automatically handles applying updates in the proper order.
See "Representing master/detail relationships" for more information on using nested datasets to support master/detail relationships in client datasets.
The IAppServer interface, which controls all communication between client datasets and providers on the application server, is mostly stateless. When an application is stateless, it does not "remember" anything that happened in previous calls by the client. This stateless quality is useful if you are pooling database connections under MTS, because your application server does not need to distinguish between database connections for persistent information such as record currency. Similarly, this stateless quality is important when you are sharing remote data module instances between many clients, as occurs with MTS just-in-time activation, object pooling, or typical CORBA servers.
However, there are times when you want to maintain state information between calls to the application server. For example, when requesting data using incremental fetching, the provider on the application server must "remember" information from previous calls (the current record).
This is not a problem if the remote data module is configured so that each client has its own instance. When each client has its own instance of the remote data module, there are no other clients to change the state of the data module between client calls.
However, it is reasonable to want the benefits of sharing remote data module instances while still managing persistent state information. For example, you may need to use incremental fetching to display a dataset that is too large to fit in memory at one time.
Before and after any calls to the IAppServer interface that the client dataset sends to the application server (AS_ApplyUpdates, AS_Execute, AS_GetParams, AS_GetRecords, or AS_RowRequest), it receives an event where it can send or retrieve custom state information. Similarly, before and after providers respond to these client-generated calls, they receive events where they can retrieve or send custom state information. Using this mechanism, you can communicate persistent state information between client applications and the application server, even if the application server is stateless. For example, to enable incremental fetching in a stateless application server, you can do the following:
TDataModule1.ClientDataSet1BeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
var
CurRecord: TBookMark;
begin
with Sender as TClientDataSet do
begin
CurRecord := GetBookmark; { save the current record }
try
Last; {locate the last record in the new packet }
OwnerData := FieldValues['Key']; { Send key value to the application server }
GotoBookmark(CurRecord); { return to current record }
finally
FreeBookmark(CurRecord);
end;
end;
end;
TRemoteDataModule1.Provider1BeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
begin
with Sender as TProvider do
DataSet.Locate('Key', OwnerData, []);
end;
Note: The previous example uses a key value to mark the end of the record set rather than a bookmark. This is because bookmarks may not be valid between IAppServer calls if the server application pools database handles.
If you want to create Web-based clients for your multi-tiered database application, you must replace the client tier with a special Web applications that acts simultaneously as a client to the application server and as a Web server application that is installed with a Web server on the same machine. This architecture is illustrated in Figure 14.1.
There are two approaches that you can take to build the MIDAS Web application:
These two approaches are very different. Which one you choose depends on the following considerations:
Caution: Your Web client application may look and act differently when viewed from different browsers. Test your application with the browsers you expect your end-users to use.
The MIDAS architecture can be combined with Delphi's ActiveX features to distribute a client application as an ActiveX control.
When you distribute your client application as an ActiveX control, create the application server as you would for any other multi-tiered application. The only limitation is that you will want to use DCOM, HTTP, or sockets as a communications protocol, because you can't count on client machines having installed the OLEnterprise or CORBA runtime software. For details on creating the application server, see "Creating the application server".
When creating the client application, you must use an Active Form as the basis instead of an ordinary form. See "Creating an Active Form for the client application", below, for details.
Once you have built and deployed your client application, it can be accessed from any ActiveX-enabled Web browser on another machine. For a Web browser to successfully launch your client application, the Web server must be running on the machine that has the client application.
If the client application uses DCOM to communicate between the client application and the application server, the machine with the Web browser must be enabled to work with DCOM. If the machine with the Web browser is a Windows 95 machine, it must have installed DCOM95, which is available from Microsoft.
Any Web browser that can run Active forms can run your client application by specifying the .HTM file that was created when you deployed the client application. This .HTM file has the same name as your client application project, and appears in the directory specified as the Target directory.
MIDAS clients can request that the application server provide data packets that are coded in XML instead of OleVariants. By combining XML-coded data packets, special javascript libraries of database functions, and Delphi's Web server application support, you can create thin client applications that can be accessed using a Web browser that supports javascript. These applications make up Delphi's InternetExpress support.
Before building an InternetExpress application, you should understand Delphi's Web server application architecture. This is described in "Creating Internet server applications".
On the InternetExpress page of the component palette, you can find a set of components that extend this Web server application architecture to act as a MIDAS client. Using these components, the Web application generates HTML pages that contain a mixture of HTML, XML, and javascript. The HTML governs the layout and appearance of the pages seen by end users in their browsers. The XML encodes the data packets and delta packets that represent database information. The javascript allows the HTML controls to interpret and manipulate the data in these XML data packets.
If the InternetExpress application uses DCOM to connect to the application server, you must take additional steps to ensure that the application server grants access and launch permissions to its clients. See "Granting permission to access and launch the application server" for details.
Tip: You can use the components on the InternetExpress page to build Web server applications with "live" data even if you do not have an application server. Simply add the provider and its dataset to the Web module.
The following steps describe how to build a Web application that creates HTML pages for allowing users to interact with the data from an application server via a javascript-enabled Web browser.
The HTML pages generated by the InternetExpress components and the Web items they contain make use of several javascript libraries that ship with Delphi:
These libraries can be found in the Source/Webmidas directory. Once you have installed these libraries, you must set the IncludePathURL property of all MIDAS page producers to indicate where they can be found.
It is possible to write your own HTML pages using the javascript classes provided in these libraries instead of using Web items to generate your Web pages. However, you must ensure that your code does not do anything illegal, as these classes include minimal error checking (so as to minimize the size of the generated Web pages).
The classes in the javascript libraries are an evolving standard, and are updated regularly. If you want to use them directly rather than relying on Web items to generate the javascript code, you can get the latest versions and documentation of how to use them from CodeCentral at www.borland.com/CodeCentral.
Requests from the InternetExpress application appear to the application server as originating from a guest account with the name IUSR_computername, where computername is the name of the system running the Web application. By default, this account does not have access or launch permission for the application server. If you try to use the Web application without granting these permissions, when the Web browser tries to load the requested page it times out with EOLE_ACCESS_ERROR.
Note: Because the application server runs under this guest account, it can't be shut down by other accounts.
To grant the Web application access and launch permissions, run DCOMCnfg.exe, which is located in the System32 directory of the machine that runs the application server. The following steps describe how to configure your application server:
The XML broker serves two major functions:
Before the XML broker can supply XML data packets to the components that generate HTML pages, it must fetch them from the application server. To do this, it uses the IAppServer interface of the application server, which it acquires through a connection component. You must set the following properties so that the XML producer can use the application server's IAppServer interface:
Two properties let you indicate what you want to include in data packets:
The components that generate HTML and javascript for the InternetExpress application automatically use the XML broker's XML data packet once you set their XMLBroker property. To obtain the XML data packet directly in code, use the RequestRecords method.
Note: When the XML broker supplies a data packet to another component (or when you call RequestRecords), it receives an OnRequestRecords event. You can use this event to supply your own XML string instead of the data packet from the application server. For example, you could fetch the XML data packet from the application server using GetXMLRecords and then edit it before supplying it to the emerging Web page.
When you add the XML broker to the Web module (or data module containing a TWebDispatcher), it automatically registers itself with the Web dispatcher as an auto-dispatching object. This means that, unlike other components, you do not need to create an action item for the XML broker in order for it to respond to update messages from a Web browser. These messages contain XML delta packets that should be applied to the application server. Typically, they originate from a button that you create on one of the HTML pages produced by the Web client application.
So that the dispatcher can recognize messages for the XML broker, you must describe them using the WebDispatch property. Set the PathInfo property to the path portion of the URL to which messages for the XML broker are sent. Set MethodType to the value of the method header of update messages addressed to that URL (typically mtPost). If you want to respond to all messages with the specified path, set MethodType to mtAny. If you don't want the XML broker to respond directly to update messages (for example, if you want to handle them explicitly using an action item), set the Enabled property to False. For more information on how the Web dispatcher determines which component handles messages from the Web browser, see "Dispatching request messages".
When the dispatcher passes an update message on to the XML broker, it passes the updates on to the application server and, if there are update errors, receives an XML delta packet describing all update errors. Finally, it sends a response message back to the browser, which either redirects the browser to the same page that generated the XML delta packet or sends it some new content.
A number of events allow you to insert custom processing at all steps of this update process:
Each MIDAS page producer generates an HTML document that appears in the browsers of your application's clients. If your application includes several separate Web documents, use a separate MIDAS page producer for each of them.
The MIDAS page producer is a special page producer component. As with other page producers, you can assign it as the Producer property of an action item or call it explicitly from an OnAction event handler. For more information about using content producers with action items, see "Responding to request messages with action items". For more information about page producers, see "Using page producer components".
Unlike most page producers, the MIDAS page producer has a default template as the value of its HTMLDoc property. This template contains a set of HTML-transparent tags that the MIDAS page producer uses to assemble an HTML document (with embedded javascript and XML) including content produced by other components. Before it can translate all of the HTML-transparent tags and assemble this document, you must indicate the location of the javascript libraries used for the embedded javascript on the page. This location is specified by setting the IncludePathURL property.
You can specify the components that generate parts of the Web page using the Web page editor. Display the Web page editor by double-clicking the Web page component or clicking the ellipsis button next to the WebPageItems property in the Object Inspector.
The components you add in the Web page editor generate the HTML that replaces one of the HTML-transparent tags in the MIDAS page producer's the default template. These components become the value of the WebPageItems property. After adding the components in the order you want them, you can customize the template to add your own HTML or change the default tags.
The Web page editor lets you add Web items to your MIDAS page producer and view the resulting HTML page. Display the Web page editor by double-clicking on a MIDAS page producer component.
Note: You must have Internet Explorer 4 or better installed to use the Web page editor.
The top of the Web page editor displays the Web items that generate the HTML document. These Web items are nested, where each type of Web item assembles the HTML generated by its subitems. Different types of items can contain different subitems. On the left, a tree view displays all of the Web items, indicating how they are nested. On the right, you can see the Web items included by the currently selected item. When you select a component in the top of the Web page editor, you can set its properties using the Object Inspector.
Click the New Item button to add a subitem to the currently selected item. The Add Web Component dialog lists only those items that can be added to the currently selected item.
The MIDAS page producer can contain one of two types of item, each of which generates an HTML form:
Items you add to TDataForm display data in a multi-record grid (TDataGrid) or in a set of controls each of which represents a single field from a single record (TFieldGroup). In addition, you can add a set of buttons to navigate through data or post updates (TDataNavigator), or a button to apply updates back to the Web client (TApplyUpdatesButton). Each of these items contains subitems to represent individual fields or buttons. Finally, as with most Web items, you can add a layout grid (TLayoutGroup), that lets you customize the layout of any items it contains.
Items you add to TQueryForm display application-defined values(TQueryFieldGroup) or a set of buttons to submit or reset those values (TQueryButtons). Each of these items contains subitems to represent individual values or buttons. You can also add a layout grid to a query form, just as you can to a data form.
The bottom of the Web page editor displays the generated HTML code and lets you see what it looks like in a browser (IE4).
The Web items that you add using the Web page editor are specialized components that generate HTML. Each Web item class is designed to produce a specific control or section of the final HTML document, but a common set of properties influences the appearance of the final HTML.
When a Web item represents information from the XML data packet (for example, when it generates a set of field or parameter display controls or a button that manipulates the data), the XMLBroker property associates the Web item with the XML broker that manages the data packet. You can further specify a dataset that is contained in a dataset field of that data packet using the XMLDataSetField property. If the Web item represents a specific field or parameter value, the Web item has a FieldName or ParamName property.
You can apply a style attribute to any Web item, thereby influencing the overall appearance of all the HTML it generates. Styles and style sheets are part of the HTML 4 standard. They allow an HTML document to define a set of display attributes that apply to a tag and everything in its scope. Web items offer a flexible selection of ways to use them:
color: red.
H2 B {color: red}
.MyStyle {font-family: arial; font-weight: bold; font-size: 18px }
The entire set of definitions is maintained by the MIDAS page producer as its Styles property. Each Web item can then reference the styles with user-defined names by setting its StyleRule property.
H2 B {color: red}
.MyStyle {font-family: arial; font-weight: bold; font-size: 18px }
The entire set of definitions is maintained by the MIDAS page producer as its Styles property. Each Web item can then reference the styles with user-defined names by setting its StyleRule property.
Another common property of Web items is the Custom property. Custom includes a set of options that you add to the generated HTML tag. HTML defines a different set of options for each type of tag. The VCL reference for the Custom property of most Web items gives an example of possible options. For more information on possible options, use an HTML reference.
The template of a MIDAS page producer is an HTML document with extra embedded tags that your application translates dynamically. Initially, the MIDAS page producer generates a default template as the value of the HTMLDoc property. This default template has the form
<HTML> <HEAD> </HEAD> <BODY> <#INCLUDES> <#STYLES> <#WARNINGS> <#FORMS> <#SCRIPT> </BODY> </HTML>
The HTML-transparent tags in the default template are translated as follows:
<#INCLUDES> generates the statements that include the javascript libraries. These statements have the form
<SCRIPT language=Javascript type="text/javascript" SRC="IncludePathURL/xmldom.js"> </SCRIPT> <SCRIPT language=Javascript type="text/javascript" SRC="IncludePathURL/xmldb.js"> </SCRIPT> <SCRIPT language=Javascript type="text/javascript" SRC="IncludePathURL/xmlbind.js"> </ SCRIPT>
<#STYLES> generates the statements that defines a style sheet from definitions listed in the Styles or StylesFile property of the MIDAS page producer.
<#WARNINGS> generates nothing at runtime. At design time, it adds warning messages for problems detected while generating the HTML document. You can see these messages in the Web page editor.
<#FORMS> generates the HTML produced by the components that you add in the Web page editor. The HTML from each component is listed in the order it appears in WebPageItems.
<#SCRIPT> generates a block of javascript declarations that are used in the HTML generated by the components added in the Web page editor.
You can replace the default template by changing the value of HTMLDoc or setting the HTMLFile property. The customized HTML template can include any of the HTML-transparent tags that make up the default template. The MIDAS page producer automatically translates these tags when you call the Content method. In addition, The MIDAS page producer automatically translates three additional tags:
<#BODYELEMENTS> is replaced by the same HTML as results from the 5 tags in the default template. It is useful when generating a template in an HTML editor when you want to use the default layout but add additional elements using the editor.
<#COMPONENT Name=WebComponentName> is replaced by the HTML that the component named WebComponentName generates. This component can be one of the components added in the Web page editor, or it can be any component that supports the IWebContent interface and has the same Owner as the MIDAS page producer.
<#DATAPACKET XMLBroker=BrokerName> is replaced with the XML data packet obtained from the XML broker specified by BrokerName. When, in the Web page editor, you see the HTML that the MIDAS page producer generates, you see this tag instead of the actual XML data packet.
In addition, the customized template can include any other HTML-transparent tags that you define. When the MIDAS page producer encounters a tag that is not one of the seven types it translates automatically, it generates an OnHTMLTag event, where you can write code to perform your own translations. For more information about HTML templates in general, see