Creating a Delphi Component for a .net XML dataset
Wrapping up the DNdataset
The DNdataset, described in detail in another paper,
can wrap up a XMLdataset as returned by a .NET webservice. The objects of this
class need the tHTTPrio component to get to the webservice supplying the
dataset. I will build a component which descends from the tHHTPrio
component, manages a DNdataSetobject and publishes all data relevant to other
Delphi components.
The component invokes the webservice via its public Get and Update
method. The component internally keeps an invocation state to guide the process
type
tDataRequestState = (RequestLoad, RequestSave, RequestNone);
Get
Calling the Get method will set the component's state and fire the
OnRequestInvocation event. In the assigned eventhandler the user of the
component is requested to invoke the method returning the XML dataset. To
conclude the state is set back to Request None.
procedure TGekkoDotNetDataSet.Get;
begin
fDataRequestState:= RequestLoad;
if Assigned(fOnRequestGetInvocation) then
fOnRequestGetInvocation(self);
fDataRequestState:= RequestNone;
end;
Intercepting the response from a webservice
The responses from the webservice can be caught in the virtual
DoAfterExecute method of the tRio class. The response contains all the
XML the DNdataset needs to fill its values. I override the method, read the
response to construct the object, and pass the response on to the inherited
DoAfterExecute.
procedure TGekkoDotNetDataSet.DoAfterExecute(const MethodName:
string; Response: TStream);
begin
case fDataRequestState of
RequestLoad : begin
fDNdataset:= tDNdataSet.Create(self);
fDNdataset.CreateFromStream(Response);
end;
end;
inherited DoAfterExecute(MethodName, Response);
end;
Update
Calling the Update method sets, after a check if there are any updates
available, the state to RequestSave and it will fire the
OnRequestUpdateInvocation event. The user of the component is requested to
invoke the update method. The signature of the update method is fixed. It has one
parameter, an xmlDocument typed by a schema. The component
will provide the actual contents of this document.
procedure TGekkoDotNetDataSet.Update;
begin
if fDNdataSet.IsUpdated then
begin
fDataRequestState:= RequestSave;
if Assigned(fOnRequestUpdateInvocation) then
fOnRequestUpdateInvocation(self);
fDataRequestState:= RequestNone;
end;
end;
Intercepting the request to a webservice.
The request to the webservice can be intercepted in the virtual
DoBeforeExecute method of tRio. The component does override this
method. This takes some extra attention as Borland decided to change the
signature of this method when moving from D6 to D7. In D6 the request is a string
and the Response is a stream. The
request also is a var parameter in D6, which implies that the method can effectively
change the request. Which is what the component wants to do, it will substitute the
parameter with the UpdateXML document created by the DNdataSet object.
{$IFDEF VER140} { Delphi 6 }
procedure TGekkoDotNetDataSet.DoBeforeExecute(const
MethodName: string; var Request: InvString);
begin
case fDataRequestState of
RequestLoad : FreeAndNil(fDNdataSet);
RequestSave : Request :=
AnsiReplaceStr(Request, '<' + fParamName + '/>',
fDNdataSet.UpdateXML(fParamName));
end;
inherited DoBeforeExecute(MethodName, Request);
end;
{$ENDIF}
After which the inherited DoBeforeExecute is called.
There are many benefits in working with the request as a stream. Borland
chose only to change the DoBeforeEvent method, not the signature of the
OnBeforeExecute event of tHTTPrio componenet. To quote rio.pas :
{ Ideally we would change the signature of this event to take a Stream.
The change to stream was necessary for attachment and encoding support.
And it makes the event consistent.... However, for the sake of
backward compatibility.... }
For the sake of backward compatibility the signature of the associated event
handler, OnBeforeExecute did not change. The implementation of
the method changed, the fact that the OnbeforeExcute signature had a var
parameter is neglected in D7.
{ NOTE: We ignore the var WideString passed in... ???? }
Which rises a version problem. I will have to create a separate override of
this method for D6 and D7. Using compiler directives the right version of the
method will be used when the component is build.
{$IFDEF VER150} {Delphi 7}
procedure TGekkoDotNetDataSet.DoBeforeExecute(const MethodName: string; Request:
TStream);
var RequestAsString : string;
StrStrm : TStringStream;
begin
case fDataRequestState of
RequestLoad : FreeAndNil(fDNdataSet);
RequestSave : begin
StrStrm := TStringStream.Create('');
try
StrStrm.CopyFrom(Request, 0);
Request.Position := 0;
RequestAsString := AnsiReplaceStr(StrStrm.DataString,
'<' + fParamName + '/>',
fDNdataSet.UpdateXML(fParamName));
StrStrm.Position:= 0;
StrStrm.WriteString(RequestAsString);
Request.Position:= 0;
Request.CopyFrom(StrStrm, 0);
finally
StrStrm.Free;
end;
end;
end;
inherited DoBeforeExecute(MethodName, Request);
end;
{$ENDIF}
This implementation uses a temporary tStringStream. The request is
copied into this stream after which the string representation, in DataString,
can be accessed. In this string the parameter is substituted with the
xmlDocument generated by the DNdataSet. The resulting string is written back to
the string-stream. I start writing at position 0 so the new request is copied
over the original request. I make the assumption that the request always grows
in size. When the request is ready it is copied back to the original request
stream. Again I overwrite the original request. The inherited DoBeforeExecute
will receive the changed request.
To sum this up
protected
{ Protected declarations }
{$IFDEF VER140} { Delphi 6 }
procedure DoBeforeExecute(const MethodName: string; var Request:
InvString); override;
{$ENDIF}
{$IFDEF VER150} {Delphi 7}
procedure DoBeforeExecute(const MethodName: string; Request:
TStream); override;
{$ENDIF}
procedure DoAfterExecute(const MethodName: string; Response: TStream); override;
The implementations are also enclosed in IFDEF directives. Your compiler will
select the right source.
Thanks for drBob for helping me getting
this to work, thanks to Alex Kovalev for making clear to me why my previous code
didn't work in D7. I have to admit I got stuck in 6.02 (pro).
Installing the component
The component, the dotnetdataset class and all other helper classes are in
the GekkoDotNet pacakage. This package can be opened in Delphi to build and
install it. The component will install itself on the webservices page. In the
next paper I will show how to use the GekkoDotNetDataSet
component.
What's next ?
|