MDAC 2.5 SDK - Technical Articles


 

Using the Customization Handler Feature in RDS 2.1

January 1999

By Kamaljit Bath

Summary

The customization handler feature, which was first introduced in RDS 2.0, enables custom logic to be executed on the server when RDS is used to work directly with remote OLE DB providers. This article describes the scenarios that are enabled by this feature and documents the steps needed to make use of it. The following topics are discussed in this document:

Introduction to the Customization Handler Feature

RDS 1.5, which ships with Microsoft® Internet Information Server 4.0, enables ADO consumers to work with remote OLE DB providers running in separate processes or on separate machines connected over HTTP or DCOM protocols. Client-side applications have the option of working directly with an OLE DB provider on the remote process/machine or with a COM component on the remote process/machine that uses ADO recordsets as input/output parameters or return values.

When working in the first mode (directly with a remote OLE DB provider, as described above), the client-side application supplies all the information needed by the specified provider, such as the connection string and the command string. In a sense, this extends the traditional client/server model to work over distributed networks. The client specifies the data store to work with and the commands to be executed, and the server simply executes these commands and returns the results, as ADO recordsets, to the client.

In the second case (working with a COM component that uses ADO recordsets), the client-side application invokes public methods only on the middle-tier component and uses the ADO recordset just as a parameter value. This is true three-tier computing, where the presentation and user interface parts stay in the client-side application, business logic in the middle-tier COM component, and the persisted state of data and referential integrity checking remains in the data store.

Starting with RDS 2.0, developers are provided with another data access paradigm, where they can write their own server-side business-rules logic and still have their client-side applications work directly with the remote OLE DB provider without going through a business object. This enables the following scenarios:

Implementation Details for the Customization Handler Feature

When RDS is used by client-side applications to work directly with remote OLE DB providers, an RDSServer.DataFactory object on the Web server handles client requests. The DataFactory object implements Query and Execute methods that can open a new recordset against a user-specified OLE DB data source object by running a user-specified command. It also implements the methods SubmitChanges and Synchronize, which can be used to apply the changes made to a recordset to a user-specified OLE DB data source object. (The update feature works only against SQL-based OLE DB providers—for example, the OLE DB Provider for ODBC and the OLE DB Provider for SQL Server.)

Writing a custom handler object that implements a specific interface called IDataFactoryHandler can enhance the DataFactory functionality. Developers can build their own custom objects to implement the IDataFactoryHandler interface. These objects are then implicitly called by DataFactory and used to extend the default functionality. This is illustrated by the following code snippet, where a client-side application is opening an ADO Recordset based on a remote OLE DB provider.

Dim AdoRs as ADODB.Recordset
AdoRs.ActiveConnection = "Handler=MyHandler.obj;Provider=MS Remote;Remote Server=http://Hyperspace;DSN=AdvWorks;"
AdoRs.Source = "Select * from Products"
AdoRs.Open

In the above code segment, the developer has specified that the default handler object to be used on the server side is the MyHandler.obj object. When this code is executed, the DataFactory object is called by default on the server. However, before the Query method on the DataFactory object opens the recordset, it creates the MyHandler.obj object and then calls QueryInterface for its IDataFactoryHandler interface. On this interface, it calls the GetRecordset method, passing in the connect and command strings. The user-written code inside the handler will open an ADO Recordset and pass it back to DataFactory. The user can use ADO events inside the handler to customize the behavior of the Connection and Recordset objects.

The following table shows which IDataFactoryHandler methods are called by each of the DataFactory methods.

DataFactory method IDataFactoryHandler method called
Query GetRecordset
SubmitChanges Reconnect
Execute OpenConnection
Synchronize OpenConnection

By default, MDAC 2.1 clients will use the new DataFactory methods Execute and Synchronize.  If the MDAC version on the server is older than version 2.1, it will return an error stating that the method does not exist.  If a 2.1 client gets this error, it either will retry with Query or SubmitChanges or, if these older methods cannot satisfy the request, will return an error.  For example, if the client wants to execute a parameterized query, the client must use Execute, because Query does not support this.

Architecture Diagram

When the client code uses RDS, without specifying the handler, to work with remote OLE DB providers, the DataFactory executes the client request. The DataFactory uses ADO to work with the underlying OLE DB provider.

When the client code uses RDS to work with remote OLE DB providers and also specifies the handler to be used, the client request is still executed by the DataFactory. The DataFactory in turn uses the specified handler to service the client requests.

Custom Handler

An RDS server custom handler is a COM Automation object that implements the IDataFactoryHandler interface. It must have its ProgID registered in the server's registry as NewHandler Progid under the following key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DataFactory\HandlerInfo \safehandlerlist

To use the specified handler, the client code used to open an ADO Recordset must include the tag "handler=<handler progid>;" in the connection string. If RDS.Datacontrol is used by the client to open the Recordset, the Handler property must be set to the handler's ProgID. If the client is coding directly to OLE DB, the connection string must contain the "handler=<handler progid>;" tag.

Server administrators can require the use of a handler with an RDS server simply by inserting the entry "HandlerRequired=1" and/or the DefaultHandler entry in the following key in the server's registry:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DataFactory\HandlerInfo

If the entry "HandlerRequired=1" is inserted and there is no DefaultHandler, then all RDS requests that do not include the "handler=<handler progid>;" tag will be rejected. And <handler progid> must point to a valid custom handler object that is included in the safehandlerlist registry entry described above.

MSDFMAP.Handler

RDS is typically used to work with OLE DB providers for SQL-based data stores. The DataFactory object can directly open and update rowsets from such data stores. To secure such data stores from direct exposure to Internet clients, MDAC 2.0 installs a default custom handler object—MSDFMAP.Handler. The behavior of this handler is driven by the file Msdfmap.ini, also installed with MDAC. The Msdfmap.ini file is installed in the Windows directory (C:\Winnt, for example). This .ini file can be configured by the server administrator to customize the behavior of the MSDFMAP.Handler object.

Configuring MSDFMAP.Handler

Changing the parameters in the supplied .ini file can modify the behavior of this handler. The .ini file(s) must reside in the Windows directory (C:\Winnt, for example).

Sample Msdfmap.ini file:

;[connect name] will modify the connection if rdsControl.connect="name"
;[connect default] will modify the connection if name is not found
;[sql name] will modify the Sql if RDSCONTROL.sql="name(args)"
;[sql default] will modify the Sql if name is not found
;Override strings: Connect, UserId, Password, Sql.
;Only the Sql strings support parameters using "?"
;The override strings must not equal "" or they are ignored
;A Sql entry must exist in each sql section or the section is ignored
;An Access entry must exist in each connect section or the section is ignored
;Access=NoAccess
;Access=ReadOnly
;Access=ReadWrite
;[userlist name] allows specific users to have special access
;The Access is computed as follows:
;  (1) First take the access of the connect section
;  (2) If a user entry is found, it will override

[connect default]
;If we want to disable unknown connect values, we set Access to NoAccess
Access=NoAccess
[sql default]
;If we want to disable unknown sql values, we set Sql to an invalid query.
Sql=" "

[connect CustomerDatabase]
Access=ReadWrite
Connect="DSN=AdvWorks"

[sql CustomerById]
Sql="SELECT * FROM Customers WHERE CustomerID = ?"
[connect AuthorDatabase]
Access=ReadOnly
Connect="DSN=MyLibraryInfo;UID=MyUserID;PWD=MyPassword"

[userlist AuthorDatabase]
Administrator=ReadWrite

[sql AuthorById]
Sql="SELECT * FROM Authors WHERE au_id = ?"

The administrator can change this .ini file to install customized DataFactory behavior on the server. For example, when a client sends in the following request, it is handled as described below:

Rs.open "CustomerById(2)", "Handler=MSDFMAP.Handler;Provider=MS Remote;Remote Server=http://mysite;Data Source=CustomerDatabase"

With the sample .ini file shown above, this is equivalent to the following command:

Rs.open "SELECT * FROM Customers WHERE CustomerId = 2", "DSN=AdvWorks"

The administrator can add new sections to the .ini file or change existing settings to define new mappings. Another option is to write a completely new handler object. The process for writing a new handler is described later in this article. The administrator can also have multiple .ini files. If a different .ini file is to be used (instead of the supplied Msdfmap.ini, which is used by default), the connection string at the client must specify the .ini file name. Example:

RDSCONTROL.Handler = "MSDFMAP.Handler,myOtherNameSpace.ini"

Or:

Rs.open "CustomerById(2)", "Provider=MS Remote;Remote Server=http://hyperspace;Handler=MSDFMAP.Handler,myOtherNameSpace.ini;Data Source=CustomerDatabase"

Note   All .ini files to be used with MSDFMAP.Handler must exist in the %SYSTEMROOT% directory.

Modifying the .ini File

The following points explain the workings of the .ini file:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DataFactory \HandlerInfo

Absence of this key—or if the value of the HandlerRequired key is 0—means that the use of the "Handler=" tag is not mandatory. RDS is thus enabled to work in the same manner as it did in RDS 1.5.

Using the Default Handler Shipped with RDS

A DataFactory handler is a COM object that implements the IDataFactoryHandler interface. The following files are included in the Data Access SDK so that users can create their own DataFactory handlers:

Source code (for the Visual Basic and Microsoft Visual C++® versions) similar to that of the default handler, MSDFMAP.Handler, is also included in the SDK. This can be used as a reference for implementing new handlers.

Msdfhdl.idl

[
   uuid(D80DE8B3-0001-11d1-91E6-00C04FBBBFB3),
   version(1.0)
]
library MSDFHDL
{
   importlib("stdole32.tlb");
   importlib("stdole2.tlb");

   // ********** MSADO15.DLL must be registered on your machine ********
   // TLib : Microsoft ActiveX Data Objects 2.1 Library
   // {00000200-0000-0010-8000-00AA006D2EA4}
   importlib("C:\Program Files\Common Files\System\ado\msado15.dll");

   [
      odl,
      uuid(D80DE8B5-0001-11d1-91E6-00C04FBBBFB3),
      version(1.0)
   ]
   interface IDataFactoryHandler : IUnknown
   {
      // The Recordset returned from GetRecordset:
      // must be created with CursorLocation = AdUseClientBatch,
      // and adRunAsync must NOT be used! (Use adUnknown or 8
      // in rs.Open.)
      HRESULT _stdcall GetRecordset(
         [in] BSTR conn,
         [in] BSTR args,
         [in] BSTR query,
         [out, retval] _Recordset **ppRS);

      // DataFactory will use the ActiveConnection property
      // on the recordset after calling Reconnect.
      HRESULT _stdcall Reconnect(
         [in] BSTR conn,
         [in] BSTR args,
         [in] _Recordset *pRS);

      // New method for MDAC 2.1 only
      HRESULT _stdcall OpenConnection(
         [in] BSTR conn,
         [in] BSTR args,
         [in] long lFlags,  // 1=WriteAccess, 2=ModifyQuery
         [in, out] BSTR * pQuery,
         [out, retval] _Connection **ppConnection);
    };
};

Handler Properties

None.

Handler Methods

GetRecordset

This function will create a new ADO Recordset using the arguments provided. The Recordset must be opened with adLockBatchOptimistic. The Connection and Recordset objects must have the Cursor Location property set to adUseClientBatch.

Reconnect

This function will create a new ADO Connection object and attach the given ADO Recordset object to it. The Connection must have the Cursor Location property set to adUseClientBatch.

OpenConnection

This function will create a new ADO Connection using the arguments provided. The Connection object must have the Cursor Location property set to adUseClientBatch.

Writing a New Custom Handler

The easy way to write a new custom handler is to start with the Visual Basic or Visual C++ sample handler in the SDK, and then modify it to your liking. The following example uses Visual Basic:

  1. Make a new ActiveX® DLL project.

  2. Select the Unattended Execution option in the Project:Properties dialog box. You should use the Free Threaded model for best performance on a server, but it's is not available in Visual Basic, so you'll have to use Apartment model for now.

  3. Include the "Microsoft ActiveX Data Objects Library" in the project references.

  4. Include the Msdfhdl.tlb (type library for the Interface IDataFactoryHandler) in the project references.

  5. Add "Implements IDataFactoryHandler" in the Declarations section of your class code.

  6. Add code to all of the methods and property set/get procedures on the interface.

Using ADO Events in a Visual Basic Handler

Inside the handler, the Connection object can be created using the WithEvents keyword. The developer can write custom events to be associated with this object.

Assume the following class names:

To implement ADO events in your DataFactory Handler:

  1. Add the following to the Declarations section of Class1:
    Private c2 as new Class2
    
  2. Add the following to the Declarations section of Class2:
    Public conn As New ADODB.Connection
    Private WithEvents connEvh As ADODB.Connection
    
  3. Add the following to the Class_Initialize function of Class2:
    Set connEvh = conn
    
  4. Implement whatever event functions you wish in Class2.

  5. Use c2.conn in Class1.

Note   The sample Visual Basic handler, Msdfmap, has ADO event handlers, but they are commented out. See the "Events" class.

Error Strings and Descriptions

When working with a custom handler object, the DataFactory object might generate one of the errors described in the following table. The error messages were changed in MDAC 2.1.

RDS 2.0 error string RDS 2.1 error string Explanation
A Handler is required and one was not specified. This page failed due to a server error. The server requires that a Remote Data Service Handler be specified when accessing this page. No handler has been specified. Please see the server administrator to update the page to use a handler. If the registry is marked to require that a handler be used with RDS requests and no default handler is specified, all requests that do not include the "Handler=" tag will receive this error.
The specified Handler is not marked safe. This page failed due to a server error. The server does not recognize this Remote Data Service Handler as a safe handler. Please see the server administrator to add the handler to the safe handlers list. The registry entry for all safe handlers does not include the specified handler.
The specified Handler ProgID was not found. This page failed due to a server error. The specified Remote Data Service Handler was not found. Please see the server administrator. The specified handler is not found in the server registry. (CLSIDFromProgID failed.)
The specified Handler was not registered. This page failed due to a server error. The specified Remote Data Service Handler was not registered. Please see the server administrator. The specified handler is not found in the server registry. (CoCreateInstance returned REGDB_E_CLASSNOTREG.)
The ProgID specified is not a valid Handler because it has no IDataFactoryHandler interface. This page failed due to a server error. The progId specified is not a valid Remote Data Service Handler because it has no IDataFactoryHandler interface. Please see the server administrator. The specified ProgID points to a COM object that does not implement the IDataFactoryHandler interface. (CoCreateInstance returned E_NOINTERFACE.)
The specified Handler has been denied access. This page failed due to a server error. The server has specifically denied access to the Remote Data Service Handler trying to access this page. Please see the server administrator to grant access to the specified handler. The handler specified in the connection string returned E_ACCESSDENIED from one of its methods.
N/A This page failed due to a server error. The server has specifically denied access to the default Remote Data Service Handler trying to access this page. Please see the server administrator to grant access to the default handler. The default handler returned E_ACCESSDENIED from one of its methods.  In this case, a handler was not specified in the connection string.