Distributed Information Systems. From A to Z. - Part III. How to use multiple SOAPDataModules in your Web service

By: Serge Dosyukov

Abstract: This a 3rd article shows how to build a stand-alone Web service using Indy and Delphi 7

Introduction

Copyright ) 2003 by Mike Pham

3rd article from "Distributed Information Systems. From A to Z" seria.
If you would like to find other articles, there is a list which will help you:

Have you ever wanted to have more than one TSoapDataModule instance within your single web service? This article demonstrates one technique for creating just such a service. If you haven't already looked at the SOAPDataModule demo that comes with Delphi 7, please do so before reading this article. The demo now only shows you how to create a SOAP datamodule with invokable methods in the same interface, but it also demonstrates how you can use TSoapConnection's SOAPServerIID to retrieve your interface directly from the service.

Much of this article is based on newsgroup messages from Bruneau (Jean-Marie Babet), who sheds some light on how the SOAP connection communicates with a web service. Here's the link that started this little project:

http://groups.google.com/groups?hl=en&lr=lang_en&ie=UTF-8&oe=UTF-8&selm=3bec1579_1%40dnews

The jist of the message is that TSoapConnection always gets the IAppServer interface from the TSoapDataModule, irregardless of the interface name you specify for the URL. Dispatches are made through SOAPActions first and if that fails, the URL will finally be used. Since this is the case, IAppServer will always be sent back to the client and the implementer of this interface is always the first registered data module (I think, since moving a SDM unit before others in the DPR seems to change which providers will be present).

Let's try to replicate this first before we get into the solution, since this will also highlight other techniques that could be used.

Three for the Price of One...Not!

First, let's create a new Web Service with DataSnap support in the standard way (File->New->Other, WebServices and then SOAP Server Application). We'll create the SOAP application as a Web App Debugger executable with the Class Name of TheThreeSDMs. Don't create any additional interfaces at this time.

Now that we have a skeleton SOAP server, we now need to add three SOAP Data Modules. I've named mine SDM1, SDM2 and (creatively) SDM3 (three is a good number, don't you think?). On each data module, place something that provides a provider. I'm going to use three ClientDataSets and loaded some data from the MyBase samples and also added some TDataSetProviders. Now that we have an actual SOAP server, we need to run the project at least once to register it with the WebApp Debugger. Once that's done, let's take a look at the WSDL generated for the service.

We can see that there are indeed three SOAP data modules registered. Let's now create a client to test those data modules. Start a new application per usual and slap on three TSoapConnection components, three ClientDataSets, three Data Sources and three DBGrids. Point each soap connection to each SDM in the following form: http://[servername]/soap/[interfacename]. Try to now set the provider name for any of the datasets. Chances are, it's dspSDM1 that returns, regardless of which SOAP data module you use. Again, as Bruneau says in his message, this is because it's the IAppServer endpoint that's being called, not the URL.

Ahh...a Solution!

Run-time, design-time...it never works the way you want it to work, which is to just use the URL and not the default IAppServer implementer. How do we make it do this? To answer this question, it's important the understand the mechanics of the problem itself. Notice that in D7, there's a SOAPServerIID property for TSoapConnection. This is always set to "IAppServerSOAP - {C99F4735-D6D2-495C-8CA2-E53E5A439E61}". You can change it to something else, but then you can't set the Connected property to True (you can if UseSOAPAdapter is False, but this just calls IAppServer instead of IAppServerSOAP, the new D6 interface). If you look in the SOAPConn unit, you can see that the underlying RIO component doesn't seem to every be created. If you look at the SOAPDataModule demo, you can see that you *can* set the SOAPServerIID to something else, however, you have to import the WSDL from the server and you also seem to lose design-time capabilities.

The solution lies in how the TSoapConnection gets information regarding your SOAP datamodule's interface. Normally, on the server-side, it is registered using the InvokeRegistry. When you import the WSDL, as in the demo, it too registers itself with the client-side InvokeRegistry so that at run-time, the TSoapConnection can find the correct interface to use when calling into the service (in the case of the demo, IDataMod). However, you lose design-time capabilities. We're going to fix that problem, since that seems to be our only obstacle. It's easy if you think about it: the IDE has no idea how to call the initialization section of the unit in your project, so your interface doesn't exist in the InvokeRegistry at design-time. The solution is to create a design-time package with just a unit that contains the descendant interfaces (such as ISDM2).

Open the server project again and add a new unit called uTheThreeSDMsIntf. We're going to first consolidate all the server's SDM interfaces into this unit and set the uses clause of each SDM to use this unit. We're also going to move the call to InvRegistry.RegisterInterface() to the initialization section of the mew interface unit. Here's the uTheThreeSDMsIntf interface unit's code:

unit uTheThreeSDMsIntf;

interface

uses
  InvokeRegistry, SOAPMidas;

type

  ISDM1 = interface(IAppServerSOAP)
    ['{E88F935E-94E4-4AFE-8A39-7453DB229286}']
  end;

  ISDM2 = interface(IAppServerSOAP)
    ['{063C7F1A-66B1-44C8-95A6-0FA025BF7028}']
  end;

  ISDM3 = interface(IAppServerSOAP)
    ['{6567D343-440B-42F8-931A-28735A8A3221}']
  end;

implementation

initialization
  InvRegistry.RegisterInterface(TypeInfo(ISDM1));
  InvRegistry.RegisterInterface(TypeInfo(ISDM2));
  InvRegistry.RegisterInterface(TypeInfo(ISDM3));
finalization
  InvRegistry.UnRegisterInterface(TypeInfo(ISDM3));
  InvRegistry.UnRegisterInterface(TypeInfo(ISDM2));
  InvRegistry.UnRegisterInterface(TypeInfo(ISDM1));
end.

Now that we've moved all the SDM interfaces to a central location, we now need to create a design-time package so that the IDE can call the initialization of this unit when it loads. Create a new package and set it to Design-Time Only. We need to only add our SDM interface unit, compile and install.

Now we move on to the client. We need to set the URL to just the default SOAP endpoint, so the URL should be the address to your WAD project, i.e. "http://localhost:8081/threesdms.TheThreeSDMs/soap". Also, we need to copy and paste the GUID string of the interface to each TSoapConnection's SOAPServerIID, just as in the SOAPDataModule demo. The difference now is that we have design-time connections and can populate the grids in both situations. One last thing to do in the client, and that is to add the uTheThreeSDMsIntf unit to the uses clause. I've included in the download a unit called uSoapIntf, which registers a property editor so that the SOAPServerIID can display all registered interfaces at the client-side *and* I can now connect to the SOAP server, as the screen-shot below shows. This saves a lot of copy-paste, but it also displays all interfaces. You can improve it if you want to check that the unit name exists only in the current project.

Whoo-hoo! I can now set the ProviderName property to each provider and it works at both design-time and run-time! That is basically the jist of it! Questions, comments, suggestions, please contact me at [mpham_at_argosoftware-dot-com].

Example Project is here.


Server Response from: ETNASC02