Dynamically Invoking a SOAP method by name?
Asked Answered
D

1

15

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?

Dynamics answered 28/6, 2012 at 15:11 Comment(2)
It will be interesting to see if anyone comes up with such, but I just wrote wrapper routines like you have to "hide" the details.Euphorbia
@dahook: Very nicely written first post. Voted up. Welcome to SO.Millian
D
4

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

Dynamics answered 28/6, 2012 at 22:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.