I am using Delphi XE2 to communicate with a fairly large SOAP service. I've successfully imported the wsdl and everything is working just fine. However, I find myself writing a lot of similar code. I would like to have a generic method that calls my web service. I also find it hard to multithread my code as it is now, since I have to write so much code for each type of call.
Being more of a weekend programmer I am far from mastering the in and outs of Delphi, but I think I at least have a fair understanding of RTTI, which I believe must be used to do what I want.
The web service has about 700 different methods and that is pretty much the problem. The code generated from the wsdl has methods as below:
function addPhone(const Params: addPhone): addPhoneResponse; stdcall;
function updatePhone(const Params: updatePhone): updatePhoneResponse; stdcall;
function getPhone(const Params: getPhone): getPhoneResponse; stdcall;
function removePhone(const Params: removePhone): removePhoneResponse; stdcall;
function listPhone(const Params: listPhone): listPhoneResponse; stdcall;
function addStuff(const Params: addStuff): addStuffResponse; stdcall;
function updateStuff(const Params: updateStuff): updateStuffResponse; stdcall;
...
... about 700 more of the above
Basically, there is about 700 different type of things that can be handled, and there is add,update,get,remove and list-methods for them all. With each call, there is a corresponding class that is used as parameters to the SOAP request. There is also a corresponding class for the response as you can see above.
The classes would look something like (very simplified):
addStuff = class
private
FStuff: string;
published
property stuff: string Index (IS_UNQL) read FStuff write FStuff;
end;
So when I call the web service I do for example:
procedure CreateStuff;
var
req: addStuff;
res: addStuffResponse;
soap: MyWebServicePort;
begin
// Use the function in the wsdl-generated code to create HTTPRIO
soap := GetMyWebServicePort(false,'',nil);
// Create Parameter Object
req := addPhone.Create;
req.stuff := 'test';
// Send the SOAP Request
res := soap.addStuff(req);
end;
(Yes, I know I should have try..finally and Free in there as well :-) )
Then, somewhere else in the code I need to call a different method:
procedure listStuff;
var
req: listStuff;
res: listStuffResponse;
soap: MyWebServicePort;
begin
// Use the function in the wsdl-generated code to create HTTPRIO
soap := GetMyWebServicePort(false,'',nil);
// Create Parameter Object
req := listPhone.Create;
req.stuff := 'test2';
// Send the SOAP Request
res := soap.listStuff(req);
end;
Since I know that the parameter is always a class with a name that is equivalent of the method I call, I would like to be able to do something like the metacode below in order to dynamically invoking the call. I guess it requires some RTTI magic but I have'nt been able to find a way to do it:
procedure soapRequest(Param: Something; var Response: Something);
begin
soap := GetMyWebServicePort(false,'',nil);
Response := soap.DynamicInvoke(Param.ClassName, Param);
end
Then I could do something like:
soapRequest(VarOfTypeAddStuff,VarOfTypeAddStuffResponse)
soapRequest(VarOfTypeListStuff,VarOfTypeListStuffResponse)
...
Does anyone have an idea how my calls to the webservice can be simplified?
It's really weird that I just a few hours after posting a question which I've been trying to solve myself for weeks all of a sudden just get solved by myself... I was inspired by looking around on SO, and I found this that helped me along the way: Delphi - Invoke Record method per name.
My scenario i somewhat specific, since I am calling the methods with a parameter that has the same classname as the method itself. I also wrote simpler version that communicates with a public web service. If someone is interested, You can get the code for that one here: http://www.hook.se/delphi/SoapDynamicInvoke.zip. It's kind of a useless example since doing dynamic method calls is only relevant when the web service has a lot of different methods. Nevertheless, it might be interesting to somebody :-)
Below is how I solved this for my web service. As said, it's quite specific and the code could be made more generic but this works for me.
This method is called with a TRemotable object, and then the web service is called with the method with the same name as the class name of the object.
function soapRequest(Param: TRemotable): TValue;
var
soap: AXLPort;
C: TRttiContext;
T: TRttiType;
M: TRttiMethod;
SoapParam: TArray<TValue>;
TVres: TValue;
soap: MyWebServicePort;
begin
// Use the function in the wsdl-generated code to create HTTPRIO
soap := GetMyWebServicePort(false,'',nil); C := TRttiContext.Create;
T := C.FindType('MyWebService.MyWebServicePort');
M := T.GetMethod(Param.ClassName);
SetLength(SoapParam,1);
SoapParam[0] := TValue.From(Param);
TVres := M.Invoke(TValue.From<IInterface>(soap), SoapParam);
Result := TVres;
end;
And to use the function above:
procedure DoSomeSoapCalls(Sender: TObject);
var
req1: getStuff
res1: getStuffResponse;
req2: addStuff;
res2: addStuffResponse;
res: TValue;
begin
//Request #1
req1 := getStuff.Create;
req1.stuffToGet := 'abc';
try
res := soapRequest(req1);
res1 := getStuffResponse(res.AsObject);
finally
req1.Free;
end;
Writeln(res1.someproperty);
FreeAndNil(res1);
//Request #2
req2 := addStuff.Create;
req2.StuffToAdd := 'cde';
try
res := soapRequest(req2);
res2 := addStuffResponse(res.AsObject);
finally
req2.Free;
end;
Writeln(res2.result);
FreeAndNil(res2);
end;
There is a bit of typecasting necessary, but in my case I think that I'll be pretty safe with that. Does anyone have any other comments/suggestions regarding this? I mean, this works, but there is probably ways to enhance it.
Cheers,
Dan
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With