Working with .NET data in Delphi
Webservices offer a rich world of functionality. This world is available to
the Delphi programmer with the introduction of the Webservice importer
introduced in Delphi 6, with version 6.02 also available in Delphi pro. A webservice
can work with pretty complex data, with .NET it is a snap to return and
receive complete XML datasets. Delphi does not know how to work with these
datasets natively. In this paper I will show how to work with .NET data using
the GekkoDotNetDataset componenet.
The .NET webservice
The webservice I am going to build will be a web-wrapper round a database.
The database is an Access database local on the web-server. It
contains customers, invoices and invoice details.

Methods of the webservice will expose this data to the web as strongly typed
XML datasets. Other methods will accept XML datasets to update the database. Importing these
tables in a .NET application will result in a nice XSD schema

For a step by step story how to build such a service you can read one of my
dotnetjunkies stories, for the moment I will concentrate on the actual
webmethods.
The Customers method returns a dataset containing all customers. It does so
by creating a new typed dataset object : DataSetCustomers. This object is
filled by the oleDbDataAdapter, the internals of this component do the real
access to the database.
[WebMethod]
public
DataSetCustomers Customers()
{
DataSetCustomers ds = new
DataSetCustomers();
oleDbDataAdapterCustomers.Fill(ds);
return
ds;
}
The entire resulting XML dataset is returned. One of the many nice things of
an XML dataset is that it can be serialized, it can be represented as one long
string of characters. Which is something which is very easy to transport over
the standard HTTP protocol.
To fill the contents of the invoices dataset, as described by the schema,
takes a little more effort. The three tables can live together in one
dataset but for every table another oleDbDataAdapter is needed.
[WebMethod]
public
DataSetInvoices Invoices()
{
DataSetInvoices ds = new
DataSetInvoices();
oleDbDataAdapterCustomers.Fill(ds.Customers);
oleDbDataAdapterInvoices.Fill(ds.Invoices);
oleDbDataAdapterInvoiceDetails.Fill(ds.InvoiceDetails);
return
ds;
}
Using dataAdapters all query possibilities of the database can be used. For
an example you are still invited at the dotnetjunkies. DataAdapters can also be used to write to a database. The updates to be
written are passed as a typed XML dataset. Which make the implementation of of the
webmethod a one-liner:
[WebMethod]
public void
UpDateCustomers(DataSetCustomers ds)
{
oleDbDataAdapterCustomers.Update(ds.Customers);
}
Multiple tables can be updated in one go in the UpdateInvoices method. The
order in these updates will be performed is important:
[WebMethod]
public void
UpdateInvoices(DataSetInvoices ds)
{
oleDbDataAdapterCustomers.Update(ds.Customers);
oleDbDataAdapterInvoices.Update(ds.Invoices);
oleDbDataAdapterInvoiceDetails.Update(ds.InvoiceDetails);
}
You cannot enter a new invoice if you don't know the customer yet. These
integrity checks are also performed in an .NET XML dataset object. But in there
they can
be switched off by setting the EnforceConstraints
property to false.
A .NET webservice consumer at work
In .NET you can build a windows client application which imports the
webservice and it will work perfect with all functionality of the webservice.
Which means that a windows application can update an Access database somewhere
on a webserver on the other side of the globe using plain HTTP.
The client reads that data from the webserver like this
localhost.DataSetWebService ws = new
localhost.DataSetWebService();
dataSetCustomers1.Clear();
dataSetCustomers1.Merge(ws.Customers());
The dataset is bound to a grid. Here the user can do some editing
after which the update is invoked
localhost.DataSetWebService ws = new
localhost.DataSetWebService();
ws.UpDateCustomers(dataSetCustomers1);
See this consumer at work in the dotnetjunkies story.
Importing the webservice in Delphi
It would be very nice to work with this webservice in Delphi. I will use
Delphi 6.02 pro which has full support for webservices clients. Delphi has a webservice importer which is found under the file | New |
Other | Webservices menu. After entering the URL of the webservice Delphi will generate
a unit describing the webservice.
The webservice has two types, being the Customers and the Invoices XSD
schema. The service has four methods who use these types in their parameters or
as result-type. And this is what the Delphi makes out of it :

Which looks pretty disappointing. The Return types of Customers and Invoices
webmethods are recognized as composite types Customers and Invoices. Alas, these
types contain no members at all. Things get worse with the update methods. Both
have a parameter named ds of type Invoices or Customers. The importer
generates two methods with a parameter named ds of a type named ds as well.
This ds type is declared and does not have any members either. That's not going
to work. Many webservices
work very well with Delphi but in this case it will need some extra help.
Introducing the GekkoDotNetDataSet component
I have built the GekkoDotNetDataSet component. This component is based on the
HTTPrio component and can be found in the demo code. It takes the following
approach to the problem:
- It wraps up one XML dataset.
- It provides the data to other Delphi components as (client-)datasets.
- The webservice has to have a function member which returns the typed
dataset.
- The webservice has to have an update member which accepts the typed XML
dataset in a parameter.
The component introduces two new published events and one new published
property, visible in the object inspector.
- OnRequestGetInvocation, an event which is fired when the component
requests the XMLdataset from the service.
- Paramname. A string to store the name of the parameter of the
updatemethod.
- OnRequestUpdateInvocation, an event which is fired when the
component requests the webservice to update the data.
The component has two public methods and two public properties to read and
write data
- The Get method reads the data into the componennt.
- The DataTable publishes all data in an array of Delphi (Client)datasets.
- TableCount counts the number of Delphi datasets.
- The Update method sends all updates to the webservice
This componenet is part of the GekkoDotNetPackage, it will install
itself on the webservices page.
Reading data with GekkoDotNetDataSet component
I drop two of these components on the form. One for the Web Services'
Customers dataset and the other for the Invoices dataset. Despite
it's emptiness I can use the imported Service1.pas. The GekkoDotNetDataSet
component is a HTTPrio descendent so I have to set the WSDLlocation in both
components , it will be
http://localhost/WebServices/DataService/Service1.asmx?wsdl. The
component's only new property is ParamName, it is the name of the
parameter of the Update methode, ds for both components.
The real new stuff is in two new events. These get fired when the component
Get's or Update's data. As the component does not know which
member of the webservice to invoke to read or write data it will make a callback
to the component's user. Requesting the user to do the actual invoke.
procedure TForm1.DataSetCustomersRequestGetInvocation(Sender:
TObject);
var Iservice : DataSetWEbServiceSoap;
begin
Iservice:= DataSetCustomers as DataSetWebServiceSoap;
Iservice.Customers;
end;
You have to get to the actual webservice by typecasting the component to the
interface of the service, which can be done because the component is a HTTPrio
wrapping up the webservice. The declaration of DataSetWebServiceSoap is
in the imported Service1.pas. On this interface you call the function which will
return the intended XML dataset. In the requestGetInvocation-eventhandler
of the other componenent the Invoices method will be invoked.
The click of a button will fill the form with a dataset, which dataset
depends on a radiogroup
procedure TForm1.ButtonGetClick(Sender: TObject);
var i : integer;
DNdataSet : TGekkoDotNetDataSet;
begin
case RadioGroup1.ItemIndex of
0 : DNdataSet:= DataSetCustomers;
1 : DNdataSet:= DataSetInvoices;
else DNdataSet:= nil;
end;
if Assigned(DNdataSet) then
begin
DNdataSet.Get;
CreateDataGrids(DNdataSet);
end;
end;
By calling get on the customers dataset the
DataSetCustomersRequestGetInvocation eventhandler will be executed. Which
will make the right invocation.
Now the tables of the are filled I will show what's in them. The
form has
an empty pagecontrol. For every dataset a page is added to the pagecontrol
:
procedure TForm1.CreateDataGrids(Sender: TObject);
var tP : tTabSheet;
ds : tDataSource;
dn : tDBNavigator;
dg : tDBGrid;
i : integer;
DNdataSet : TGekkoDotNetDataSet;
begin
DNdataSet:= sender as TGekkoDotNetDataSet;
if DNdataSet <> nil then
begin
for i:= 0 to DNdataSet.TableCount - 1 do
begin
tP:=tTabSheet.Create(self);
tP.PageControl:=
PageControl1;
tP.Caption:=
DNdataSet.DataTable[i].Name;
ds:=
tDataSource.Create(Self);
dn:=
tDBnavigator.Create(self);
dn.Align:= alTop;
dn.Parent:= tP;
dn.DataSource:= ds;
dg:= tDBGrid.Create(self);
dg.Align:= alClient;
dg.Parent:= tP;
dg.DataSource:= ds;
ds.DataSet:=
DNdataSet.DataTable[i];
end;
end;
end;
For every (client-)dataset in the GekkoDotNetDataSet I create a new page with
a datagrid and a dbNavigator.
When running this application I can browse and update the data in my Delphi
form.
Updating data with GekkoDotNetDataSet component
To return all updates to the webservice the component uses another event
procedure
TForm1.DataSetInvoicesRequestUpdateInvocation(Sender: TObject);
var Iservice : DataSetWEbServiceSoap;
begin
Iservice:= DataSetInvoices as DataSetWebServiceSoap;
Iservice.UpDateInvoices(ds.Create);
end;
This event is fired by the componenet when it's Update method
is called. The component does not know which member to invoke, in this
eventhandler the component' user is requested to invoke the desired method. The
ds class is declared in the imported unit. It has no members but it will do
to invoke the method.
The form sends the updates by the click of a button
procedure TForm1.ButtonSaveClick(Sender: TObject);
var DNdataSet : TGekkoDotNetDataSet;
begin
case RadioGroup1.ItemIndex of
0 : DNdataSet:= DataSetCustomers;
1 : DNdataSet:= DataSetInvoices;
else DNdataSet:= nil;
end;
if Assigned(DnDataSet) then
DNdataSet.Update;
end;
Now we have a Delphi application which works with a .NET webservice and can
read or write XML dataset data.

Inpecting the webservice's request and response
To get an idea what is going on I have set the GekkoDotNetDataSet
componenet's before- and after-
execution event to show the full SOAP request and response in explorer
windows.
The request is passed to the eventhandler as a string and the response as a stream. The ShowXML
method will send the stream to a browser, it does so by saving the xml as a
temporary file and pointing a browser to it. In the BeforeExcute event the string request
has to be written
to a stream before being sent to the ShowXML method.
procedure TForm1.HTTPRIO1BeforeExecute(const MethodName: String; var SOAPRequest: WideString);
var ts : tStringStream;
buffer : string;
begin
buffer:= SOAPrequest;
ts:= tStringStream.Create(buffer);
Showxml(ts, Send);
end;
procedure TForm1.HTTPRIO1AfterExecute(const MethodName: String;
SOAPResponse: TStream);
begin
ShowXML(SOAPResponse, Receive);
end;
Now you can see the full requests as they are sent to the .NET webservice and
the response.
(Don't) try this ay home !
The GekkoDotNetDataSet component relies on the tDNdataSet, whose internals are described in another
paper. The class has been created by trial and error in investigating the
results of .NET built webservices. If you want to use the class in your own code
please consider the following points (this is a disclaimer !)
- The dataset does (by long) not support all possible field-types.
- The functionality is based on the diffgram structure as found, this
structure is not (as far as I know) backed up by some kind of formal
specification. Special testing deserve the roworder attribute and the
localization settings.
- New updated versions of the component and these papers will appear on this
website.
You are welcome to experiment with the component and any suggestions,
remarks, comments, or other feedback will be greatly appreciated. It will all be
used in the updates.
Where are we ?
In this paper we have seen how a Delphi webservice client can work with a
webservice which works with XML (diffgram) datasets. All functionality is stored
in the GekkoDotNetDataSet component, which is based on Delphi's HTTPrio component.
What's next ?
|