我正在使用 Delphi XE2 与相当大的 SOAP 服务进行通信。我已成功导入 wsdl,一切正常。然而,我发现自己写了很多类似的代码。我想要一个调用我的网络服务的通用方法。我还发现很难像现在这样对我的代码进行多线程处理,因为我必须为每种类型的调用编写大量代码。
作为一个周末程序员,我还远远没有掌握 Delphi 的细节,但我想我至少对 RTTI 有一个相当的了解,我相信它必须用来做我想做的事情。
Web 服务有大约 700 种不同的方法,这几乎就是问题所在。从wsdl生成的代码有以下方法:
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
基本上,可以处理大约 700 种不同类型的事物,并且都有针对它们的添加、更新、获取、删除和列表方法。每次调用时,都有一个相应的类用作 SOAP 请求的参数。正如您在上面看到的,响应也有一个相应的类。
这些类看起来像(非常简化):
addStuff = class
private
FStuff: string;
published
property stuff: string Index (IS_UNQL) read FStuff write FStuff;
end;
因此,当我调用网络服务时,我会执行以下操作:
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;
(是的,我知道我应该尝试..finally 并在那里也免费:-) )
然后,在代码中的其他地方我需要调用不同的方法:
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;
由于我知道参数始终是一个类,其名称与我调用的方法等效,因此我希望能够执行类似于下面的元代码的操作,以便动态调用该调用。我想这需要一些 RTTI 魔法,但我一直找不到方法来做到这一点:
procedure soapRequest(Param: Something; var Response: Something);
begin
soap := GetMyWebServicePort(false,'',nil);
Response := soap.DynamicInvoke(Param.ClassName, Param);
end
然后我可以做这样的事情:
soapRequest(VarOfTypeAddStuff,VarOfTypeAddStuffResponse)
soapRequest(VarOfTypeListStuff,VarOfTypeListStuffResponse)
...
有人知道如何简化我对网络服务的调用吗?
最佳答案
这真的很奇怪,我在发布了一个我几周来一直试图解决自己的问题后几个小时突然就自己解决了......我受到了环顾四周的启发,并且我发现这个对我一路有帮助:Delphi - Invoke Record method per name 。
我的场景有些具体,因为我使用与方法本身具有相同类名的参数来调用方法。我还编写了与公共(public)网络服务通信的更简单的版本。如果有人感兴趣,您可以在这里获取该代码:http://www.hook.se/delphi/SoapDynamicInvoke.zip 。这是一个无用的示例,因为仅当 Web 服务具有许多不同的方法时,进行动态方法调用才有意义。尽管如此,某些人可能会感兴趣:-)
下面是我如何为我的网络服务解决这个问题。如前所述,它非常具体,代码可以变得更通用,但这对我有用。
使用 TRemotable 对象调用此方法,然后使用与该对象的类名同名的方法调用 Web 服务。
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;
并使用上面的函数:
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;
有必要进行一些类型转换,但就我而言,我认为这样做会非常安全。还有人对此有任何其他意见/建议吗?我的意思是,这可行,但可能有方法可以增强它。
干杯,
丹
关于delphi - 按名称动态调用 SOAP 方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11247619/