SOAP
And Web Services
Using
ADO.NET datasets in Delphi
Abstract
If you've experimented with Web Services, you might have hit
some Microsoft .NET based Web Services which return data all
right, but it's in the default XML format from ADO.NET. So
you end up with some XML but you have no clue what to do with
it! This article explains how you can take this XML, make
sense out of it and even display it in a DB Grid.
Scope
I'm not going to explain much about .NET, or the ADO.NET XML
format. What I'll talk about is the most probable case you'll
encounter .NET datasets: as XML returned from a .NET based
Web Service. If you're going to use .NET datasets in some
other way, you might want to read this article to get an idea
of how to make your Delphi application aware of them.
Hitting the .NET service
Note: we have changed this paper to use a different web service since
http://services.pagedownweb.com/ZipCodes.asmx.
is no longer working.
Let's start with a simple .NET service, as given in
this URL.
I've used the Web Service Importer in File | New | Other |
Web Services and generated the Pascal files. Here's the declaration
that looks odd:
ds = class(TRemotable)
private
Fschema: WideString;
published
property schema: WideString read Fschema write Fschema;
end;
DataServiceSoap = interface(IInvokable)
['{3C2B9DA7-EB7B-382D-F623-8BB040FAF360}']
function GetTitleAuthors: GetTitleAuthorsResult; stdcall;
...
end; |
The GetTitleAuthors
function returns a .NET dataset, as XML. Here, the schema
of the ds class is simply the dataset as XML in
ADO.NET's default format. This means that a .NET client could
easily get this XML and show it on a grid or a form - but
can we do this with Delphi? Let's see.
Using the .NET service
in Delphi
I've created a sample form , which looks like this:
Now we've setup the HTTPRio,
and the code behind the Get Authors button is:
procedure
TForm1.Button1Click(Sender: TObject);
begin
GetDataServiceSoap(False, '', HTTPRio1).GetTitleAuthors();
end; |
I've also added an event handler
on the HTTPRio1.OnAfterExecute like so:
procedure
TForm1.HTTPRIO1AfterExecute(const MethodName: String;
SOAPResponse: TStream);
begin
SOAPResponse.Position := 0;
Memo1.Lines.LoadFromStream(SOAPResponse);
SOAPResponse.Position := 0;
end; |
This is only to display the
returned content on to a Memo so we can figure out what to
do with it. Here's how the form looks now:
Interpreting the .NET
XML
We have to figure out how
to get Delphi to USE this data. We would like to have a Client
Data Set read the XML so we can display it all in a Grid.
For that we'll have to use XTR transforms. No, that's not
very complicated, and here's how we'll do it.
1. First we're going to save
the XML returned into an XML file. I've saved it as "data.xml".
2. Run XML mapper from the
Tools menu, and open this XML file. Here's a mega screen shot:
3. The Titles(*) means there's
multiple rows of "Titles" available. Columns available
are the subnodes visible. Let's double-click
each one of these to add them to the transformation and then
click the DataPacket from XML in the Create Menu. Here's what
it all looks like:
4. Save the Transformation using File | Save
| Transformation, as "TitleTrans.xtr". Don't try to
test the transformation yet. (There's a bug in Delphi Source
code that doesn't like SOAP namespaces in the source elements
so it doesn't show up any data).
5. We'll now FIX this bug. The XTR file is an
XML file which you can open in any text editor. Open it, and
change the first line from:
<SelectEach dest="DATAPACKET\ROWDATA\ROW"
from="\soap:Envelope\soap:Body\GetTitleAuthorsResult\diffgr:diffgram\NewDataSet\Titles">
[Change To]
<SelectEach dest="DATAPACKET\ROWDATA\ROW"
from="\Envelope\Body\GetTitleAuthorsResult\diffgram\NewDataSet\Titles"> |
The reason for this is that Delphi's XML Transform
provider does not like the "soap:" in the first
element of the "from" attribute (and all such namespaces in the "source"). That might get
fixed in some update pack, so this point might not apply
6. We're nearly there. Drop a TClientDataset,
a TXMLTransformProvider and a TDatasource on the form.
Link the Grid, the Datasource and the ClientDataset, and
set the ClientDataset's ProviderName to point to the XML Transform
Provider. 7. Set the TransformRead.TransformationFile of the
XMLTransformProvider to TitleTrans.XTR.
8. Now we need to set the
data of the XML Transform Provider at run time. Here's some
additional code in the HTTPRio's OnAfterExecute
procedure TForm1.HTTPRIO1AfterExecute(const
MethodName: String;
SOAPResponse: TStream);
var
XMLDoc: IXMLDocument;
begin
SOAPResponse.Position := 0;
Memo1.Lines.LoadFromStream(SOAPResponse);
ClientDataset1.Active := FALSE;
SOAPResponse.Position := 0;
XMLDoc := NewXMLDocument;
XMLDoc.Encoding := SUTF8;
SOAPResponse.Position := 0;
XMLDoc.LoadFromStream(SOAPResponse);
XMLTransformProvider1.TransformRead.SourceXmlDocument
:= XMLDoc.GetDOMDocument;
ClientDataset1.Active := TRUE;
end; |
You'll notice that we've created
an XML Document, loaded it from the Received SOAP stream,
and applied the transform to it. The client dataset gets data
from the provider and displays the data:
That's it!
Amazing.
Thank you.
What's next?
This transformation is very specific to this particular service
and XML schema. SO if you know what XML is going to be returned
(the format) then you can use XML mapper to generate a transformation
for it.
I haven't been able to write
a "general" transform that can be applied to ANY
.NET returned XML, but if anyone does I'd love to hear about
it.
Also, why have I used the
HTTPRio's OnAfterExecute, rather than manipulating the the
s_schema parameter? There's another bug in Delphi that doesn't
like parameters returned as XML. More revealed in this thread.
<http://groups.google.com/groups?hl=en&ie=ISO-8859-1&oe=ISO-8859-1&selm=3c76a00f%241_1%40dnews>
You can download all the code for this project at http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=17807
or at http://www.agnisoft.com/downloads
(dotnetds.zip)
-------------------------------------------------------------------------------------------------------
|