Using .NET complex types in a Delphi web service client

By: John Kaster

Abstract: In an article on the continuing rapid enhancements to Borland's Web Services solutions, John K discusses prototype support for .NET web services that use Document Literal encoding

A bit of a surprise

Although Borland surprised many people (particularly our competitors) in the industry by releasing the first RAD solution for Web Services, we knew the work had just started. If you install Delphi 6 Enterprise or the Delphi 6 Enterprise Trial, which you can download here, you will see an acknowledgement about the changing nature of SOAP and web services, and that Borland reserves the right to break interface compatibility with the SOAP implementation that shipped with Delphi 6.0.

Our work on interoperability with other SOAP vendors has continued at a furious pace. We have joined the interoperability lab for SOAP, and have been improving our interoperability daily. Our SOAP support is cross-platform, so the Kylix 2 release of our SOAP support contained improvements over the support that was provided with Delphi 6 update patch 1. I would expect additional improvements when C++ Builder 6 is released. Furthermore, as I'll discuss here, additional improvements may be available in beta form before C++ Builder 6 ships.

Show and Tell at PDC

I demonstrated improved support for the "document literal" (Doc|Lit) invokation method for SOAP at PDC. You can find more information about Borland's presence at PDC in the article "Borland exhibits at PDC" about our presence there. By default, .NET web services use Doc|Lit encoding for SOAP requests, rather than the more common Remote Procedure Call (RPC|Encoded) format, which is Borland's default for SOAP invocation. In the Delphi 6 update patch 1, we improved support for RPC|Encoded .NET Services. This recent work focused on Doc|Lit Services. Let me show you want it can do.

One of the .NET-based web services listed on XMethods is a zipcode resolving web service. Of course, this only works for US zipcodes. You can find it at

The WSDL Importer has been improved since the first update patch. Let's run it and see.

WSDL Importer page 1
Putting in the WSDL url

After putting in the location of WSDL file to import (which can be either an actual file available somewhere on your network, or a URL), click the Next button. The wizard will retrieve the WSDL and parse it, showing the results on the following page.

WSDL Importer page 2
Reviewing the source that will be generated

On the second page, you can see a tree view of all the methods, types, and complex types that are generated by importing the WSDL.

Complex type support

The following code is generated by the importer from the complex type defined in the WSDL for the .NET web service, which you can see expanded in the tree view of the imported types and methods above.

USPSAddress = class(TRemotable)
  FStreet: String;
  FCity: String;
  FState: String;
  FShortZIP: String;
  FFullZIP: String;
  property Street: String read FStreet write FStreet;
  property City: String read FCity write FCity;
  property State: String read FState write FState;
  property ShortZIP: String read FShortZIP write FShortZIP;
  property FullZIP: String read FFullZIP write FFullZIP;

Writing the client

With this language binding for the web service, we can easily write the client-side code for it. Create a new project. Then, let's create the interface we need. The following form has four buttons for invoking the various methods of the web service, four TEdit controls for the input values to pass to the web service methods, and a TMemo control for displaying the results. Also drop a THTTPRIO component from the Web Services tab on the form. Assign its URL to When you click on the Service property, you will see a drop down containing one value, "ZipCodeResolver." Once you select the service and click on the Port property, you will see the value "ZipCodeResolverSoap." With the latest version of the HTTPRIO component, you don't have to assign the Service and Port properties in advance if there is only one service/port combination. The updated version of the support is able to determine there is only one possible combination and automatically selects it. When multiple service/port combinations are defined, you will still need to assign it.

Client interface

Add the unit zipcoderesolver.pas to the project and use it from the unit you just created. There is a new option for the HTTPRIO converter called soDocument that is automatically assigned when the updated version of THTTPRIO.QueryInterface determines it is needed. When the RIO gives you back an interface, it checks in the importer registers that interface as Doc. If yes, the RIO modifies itself to be [soDocument] before returning the interface to you.

function THTTPRIO.QueryInterface(const IID: TGUID; out Obj): HResult;
  Result := inherited QueryInterface(IID, Obj);
  { Here we default the SOAPAction either the namespace or registered SOAPAction
    of the interface we just handed out - Of course, this value will/may be
    overriden on a call by call basis }
  if Result = 0 then
    FHTTPWebNode.SoapAction := InvRegistry.GetActionURIOfIID(IID);
  { We also override the invoke options to handle document-style services}
  if ioDocument in InvRegistry.GetIntfInvokeOptions(IID) then
    FDOMConverter.Options := FDOMConverter.Options + [soDocument];

Click each button to create the stubs for the methods shown here. I've named the buttons to make the code attached to each action more clear.

procedure TForm1.ButtonShortZipClick(Sender: TObject);
  Service: ZipCodeResolverSoap;
  Service := (HTTPRIO1 as ZipCodeResolverSoap);
  Memo1.Text := Service.ShortZipCode(AccessCode.Text, Address.Text, City.Text, State.Text);

procedure TForm1.ButtonFullZipClick(Sender: TObject);
  Service: ZipCodeResolverSoap;
  Service := (HTTPRIO1 as ZipCodeResolverSoap);
  Memo1.Text := Service.FullZipCode(AccessCode.Text, Address.Text, City.Text, State.Text)

procedure TForm1.ButtonCorrectClick(Sender: TObject);
  Service: ZipCodeResolverSoap;
  Service := (HTTPRIO1 as ZipCodeResolverSoap);
  Memo1.Text := Service.CorrectedAddressHtml(AccessCode.Text, Address.Text, City.Text, State.Text);

procedure TForm1.ButtonVersionClick(Sender: TObject);
  Service: ZipCodeResolverSoap;
  Service := (HTTPRIO1 as ZipCodeResolverSoap);
  Memo1.Text := Service.VersionInfo;

We're all set to run the application. With the values provided in the edit fields that are partially correct, we are still providing enough information to the zipcode resolver for it to find the correct information. Here's the results of pressing each button:

The correct short zipcode for Borland

The correct full zipcode for Borland

The corrected address for Borland

The version information for the zipcode resolver

You can download the source for this project and the compiled version of it from CodeCentral at, so you can run it yourself and see how it works.

When can I get it?

You're probably wondering why I'm teasing you with an article talking about code you don't currently have. It's not actually a tease. As I mentioned in the interoperability article, our R&D teams have been working full speed on support for WebServices in all our development products, and we'll be keeping the support on a "rapid release" schedule, so as the standards emerge and important interop issues are discovered, we'll continue to release new versions of our SOAP support.

What I've discussed in this article is a work in progress that will probably be enhanced further by the time the code is available. The current plan is to release it as part of our new public beta testing system on CodeCentral. Watch the Delphi SOAP newsgroup or the Community site for announcements of its availability.

In particular, watch from messages from Jean-Marie Bruneau Babet (Bruneau). He's one of the people working heavily on our Web Services support and is very active on the SOAP newsgroup.

Server Response from: ETNASC03