Динамически вызывать метод SOAP по имени?
Я использую Delphi XE2 для связи с довольно большой службой SOAP. Я успешно импортировал wsdl, и все работает отлично. Тем не менее, я нахожу, что пишу много похожего кода. Я хотел бы иметь общий метод, который вызывает мой веб-сервис. Мне также сложно многопоточно читать мой код, как сейчас, так как я должен написать столько кода для каждого типа вызова.
Будучи больше программистом на выходные, я далек от освоения Delphi, но я думаю, что, по крайней мере, у меня есть честное понимание RTTI, которое, я считаю, должно использоваться для того, чтобы делать то, что я хочу.
Веб-сервис имеет около 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;
(Да, я знаю, что мне следовало бы попробовать... наконец и бесплатно там:-))
Затем в другом месте кода мне нужно вызвать другой метод:
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)
...
Есть ли у кого-нибудь идея, как мои вызовы в веб-сервис могут быть упрощены?
Ответы
Ответ 1
Это действительно странно, что я через несколько часов после публикации вопроса, который я пытался решить сам в течение нескольких недель, внезапно просто решил сам... Я был вдохновлен, оглядываясь на SO, и я нашел это, что помогло мне в пути: Delphi - вызывать метод записи для имени.
Мой сценарий я несколько конкретный, так как я вызываю методы с параметром, который имеет то же имя класса, что и сам метод. Я также написал более простую версию, которая общается с общедоступным веб-сервисом. Если кому-то интересно, вы можете получить код для этого: http://www.hook.se/delphi/SoapDynamicInvoke.zip. Это своего рода бесполезный пример, поскольку выполнение динамических вызовов метода имеет смысл только тогда, когда веб-служба имеет множество разных методов. Тем не менее, это может быть интересно кому-то: -)
Ниже я расскажу, как я решил это для своего веб-сервиса. Как сказано, это довольно специфично, и код можно сделать более общим, но это работает для меня.
Этот метод вызывается с объектом TRemotable, а затем веб-служба вызывается с помощью метода с тем же именем, что и имя класса объекта.
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;
Существует немного приведения типов, но в моем случае я думаю, что с этим я буду в полной безопасности. Есть ли у кого-нибудь другие комментарии/предложения по этому поводу? Я имею в виду, что это работает, но, вероятно, есть способы его улучшить.
Приветствия,
Dan