2012-06-28 11 views
14

Estoy usando Delphi XE2 para comunicarme con un servicio SOAP bastante grande. He importado con éxito el wsdl y todo está funcionando bien. Sin embargo, me encuentro escribiendo un código similar. Me gustaría tener un método genérico que llame a mi servicio web. También me resulta difícil leer varias veces mi código tal como está ahora, ya que tengo que escribir tanto código para cada tipo de llamada.Invocando dinámicamente un método SOAP por su nombre?

Siendo más como programador de fin de semana, estoy lejos de dominar los entresijos de Delphi, pero creo que al menos tengo una comprensión justa de RTTI, que creo que debe usarse para hacer lo que quiero.

El servicio web tiene alrededor de 700 métodos diferentes y ese es prácticamente el problema. El código generado a partir del WSDL tiene métodos de la siguiente manera:

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 

Básicamente, hay alrededor de 700 diferentes tipos de cosas que se pueden manejar, y no añadir, actualizar, obtener, quitar y list-métodos para todos ellos . Con cada llamada, hay una clase correspondiente que se usa como parámetros para la solicitud SOAP. También hay una clase correspondiente para la respuesta, como puede ver arriba.

Las clases se vería algo así como (muy simplificada):

addStuff = class 
    private 
    FStuff: string; 
    published 
    property stuff: string Index (IS_UNQL) read FStuff write FStuff; 
    end; 

Así que cuando llamo al servicio web que hago por ejemplo:

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; 

(Sí, ya sé que debería haber intento ..finally y gratis allí también :-))

Luego, en algún otro lugar del código, necesito llamar a un método diferente:

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; 

Como sé que el parámetro siempre es una clase con un nombre que es equivalente al método que llamo, me gustaría poder hacer algo como el metacódigo siguiente para invocar dinámicamente la llamada. Supongo que requiere un poco de magia RTTI pero have'nt sido capaz de encontrar una manera de hacerlo:

procedure soapRequest(Param: Something; var Response: Something); 
begin 
    soap := GetMyWebServicePort(false,'',nil); 
    Response := soap.DynamicInvoke(Param.ClassName, Param); 
end 

entonces yo podría hacer algo como:

soapRequest(VarOfTypeAddStuff,VarOfTypeAddStuffResponse) 
soapRequest(VarOfTypeListStuff,VarOfTypeListStuffResponse) 
... 

¿Alguien tiene una idea de cómo mi las llamadas al servicio web se pueden simplificar?

+0

Será interesante ver si alguien se le ocurre tal, sino que acabo de escribir rutinas de envoltura como que tiene "ocultar" los detalles. – mj2008

+3

@dahook: primera publicación muy bien escrita. Votado arriba. Bienvenido a SO. – RobertFrank

Respuesta

4

Es realmente extraño que unas pocas horas después de haber publicado una pregunta que he estado tratando de resolver por mí mismo durante semanas, de repente me haya solucionado solo ... Me inspiré al buscar en SO, y Encontré esto que me ayudó en el camino: Delphi - Invoke Record method per name.

Mi escenario es algo específico, ya que estoy llamando a los métodos con un parámetro que tiene el mismo nombre de clase que el método en sí. También escribí una versión más simple que se comunica con un servicio web público. Si alguien está interesado, puede obtener el código aquí: http://www.hook.se/delphi/SoapDynamicInvoke.zip. Es un ejemplo inútil ya que hacer llamadas a métodos dinámicos solo es relevante cuando el servicio web tiene muchos métodos diferentes. Sin embargo, podría ser interesante para alguien :-)

A continuación se muestra cómo resolví esto para mi servicio web. Como dije, es bastante específico y el código podría hacerse más genérico, pero esto funciona para mí.

Este método se llama con un objeto TRemotable, y luego se llama al servicio web con el mismo nombre que el nombre de clase del objeto.

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; 

Y para usar la función anterior:

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; 

Hay un poco de encasillamiento es necesario, pero en mi caso creo que voy a estar bastante seguro con eso. ¿Alguien tiene otros comentarios/sugerencias con respecto a esto? Quiero decir, esto funciona, pero probablemente haya formas de mejorarlo.

Saludos,

Dan

Cuestiones relacionadas