Inicio > Aplicación, Delphi, WebService > (3/5) Generación de un cliente (WebService) en Delphi.

(3/5) Generación de un cliente (WebService) en Delphi.

Share Button

En las dos entradas anteriores de esta serie, hemos planteado el problema y hemos visto cómo generar un WebService en PHP que nos permita acceder a nuestra Base de Datos. Una vez completada la parte Servidor, vamos a comenzar a desarrollar las aplicaciones cliente que van a consumir los datos.

“ROADMAP” DE LAS ENTRADAS

Siguiendo con nuestra “hoja de ruta”, vamos a desarrollar varios clientes de escritorio en Windows, utilizando diferentes alternativas y posibilidades de las que disponemos.

web-service-overview-1

CLIENTE 1: Delphi6 + Indy + LkJSON

Nuestra primera opción va a ser una aplicación (creada en Delphi 6) que acceda al primer webservice que hemos creado aquí y para ello utilizaremos las librerías Indy.

La dirección base es la siguiente, http://neftali.clubdelphi.com/agenda/listado.php; A esa dirección le añadiremos los parámetros necesarios para obtener los datos que necesitemos.

JSONEn este caso la solución es muy simple y basta con utilizar el componente  TIdHTTP para acceder a la dirección deseada, incluyendo parámetros, según la respuesta que queramos obtener.
Para tratar la información que nos devuelve el servidor  (en JSON) utilizaremos la librería LkJSON de Leonid Koninin, que podéis encontrar en SourceForge, para extraer los datos y recorrerlos de una forma más sencilla. Aprovecharemos para llenar un TClientDataset  que nos permita mostrarlos en pantalla utilizando componentes de Base de Datos.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var
  TS:TStrings;
  FResponse: string;
  obj:TlkJSONbase;
  vLevel, i:Integer;
  Str, sURL:String;
  k:Word;
begin
  // Crear el componente para los envíos
  Self.Fhttp := TIDHttp.Create(nil);
  // ini
  Self.Fhttp.HandleRedirects := True;
  Self.Fhttp.ReadTimeout := 5000;
 
  // Crear la estructura de respuesta
  Self.FResp := TStringStream.Create('');
  TS := TStringList.Create;
  // Tratar los parámetros
  if (edtIDUsuario.Text <> '') then begin
    i := StrToIntDef(edtIDUsuario.Text, -5);
    if (i = -1) then begin  // Todos los usuarios
      sURL := DIR_BASE;
    end
    else begin  // Buscamos por ID
      sURL := DIR_BASE + '?user=' + IntToStr(i);
      cbAllUsers.Checked := False;
    end;
  end
  else begin
    if (edtNumRegistros.Value = -1) or (edtNumRegistros.Value > 0)then begin
      sURL := DIR_BASE + '?num=' + IntToStr(edtNumRegistros.Value);
    end
    else begin
      sURL := DIR_BASE;
    end;
  end;
 
  // realizar la peticion
  try
    Self.Fhttp.Post(sURL, TS, Self.FResp);

 

Como veis el código es bastante simple y fácil de entender. Creamos el componente TidHTTP, añadimos a la dirección base del WebService los parámetros, según los datos que ha seleccionado el usuario y lanzamos la petición.

Habría que capturar los posibles errores en la llamada, pero en este caso nos vamos a centrar en la respuesta y en los casos en que es correcta.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  // realizar la peticion
  try
    Self.Fhttp.Post(sURL, TS, Self.FResp);
    // recoger la respuesta (string)
    FResponse := Self.FResp.DataString;
    // La hacemos visible
    mmResponse.Lines.Text := FResponse;
 
    // Conseguir la cadena formateada (componente LkJSON).
    obj := TlkJSONbase.Create();
    // proteccion
    try
      vLevel := 0;
      obj := TlkJSON.ParseText(FResponse);
      // obtener respuesta formateada
      mmResponseOK.Lines.Text := GenerateReadableText(obj, vLevel);
      // Parsear la respuesta para almacenar los datos en un TClientDataset
      ParseResponse(TlkJSONobject(obj));
    finally
      FreeAndNil(obj);
    end;

Imagen9

Lo que hacemos en este caso es recuperar las respuesta en formato texto (pues es JSON) y después de formatearla (no es necesario, simplemente es para poder ver el resultado de forma más legible), llamamos al procedimiento ParseResponse, que lo que hace es recorrer los registros devueltos y almacenarlos en un TClientDataset (para poder mostrarlos en un DBGrid estandard).

Imagen8

El procedimiento ParseResponse  no tienen ningún secreto, y es el que tenéis a continuación.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Extraer usuarios de la respuesta
procedure TForm1.ParseResponse(AObjResp: TlkJSONobject);
var
  objUserList:TlkJSONlist;
  objUser, objtmp:TlkJSONobject;
  i:integer;
  Str:String;
begin
  // Limpiar el contenido
  cds.EmptyDataSet;
  // Objeto usuarios?
  if (AObjResp.IndexOfName('Usuarios') <> -1) then begin
    // Lo almaceamos en un tipo "lista"
    TlkJSONBase(objUserList) := AObjResp.Field['Usuarios'];
    // Recorrer la lista de usuarios.
    for i := 0 to (objUserList.Count - 1) do begin
      // Objeto usuario
      TlkJSONBase(objUser) := objUserList.Child[i];
      TlkJSONBase(objtmp) := objUser.FieldByIndex[0];
      // Añadimos un registro al CDS
      cds.Append;
      try
        // Recoger los campos del objeto
        cds.FieldByName('id').AsString := VarToStr(objtmp.Field['id'].Value);
        cds.FieldByName('nombre').AsString := VarToStr(objtmp.Field['nombre'].Value);
        cds.FieldByName('apellidos').AsString := VarToStr(objtmp.Field['apellidos'].Value);
        cds.FieldByName('telefono').AsString := VarToStr(objtmp.Field['telefono'].Value);
        cds.FieldByName('extension').AsString := VarToStr(objtmp.Field['extension'].Value);
        cds.FieldByName('departamento').AsString := VarToStr(objtmp.Field['departamento'].Value);
        cds.FieldByName('interno').AsString := VarToStr(objtmp.Field['interno'].Value);
        cds.FieldByName('email').AsString := VarToStr(objtmp.Field['email'].Value);
        // Grabar
        cds.Post;
      finally
        cds.Cancel;
      end;
    end;
  end;
end;

Aquí tenéis un pequeño vídeo del programa de ejemplo funcionando.

Ejemplo1_funcionando

Al final os dejo los links para descargar los fuentes y el ejecutable del proyecto.

 

CLIENTE 2 (Delphi XE5 + Componentes REST):

Para probar este primer Webservice (http://neftali.clubdelphi.com/agenda/listadoREST2.php) he creado otro cliente; En este caso utilizando Delphi XE5.

Las últimas versiones de Delphi (creo recordar que desde la XE2 en adelante) ya poseen integrada una librería para tratar JSON y también componentes específicos para conectarse con servidores REST. En concreto, para este ejemplo vamos a ver que, con pocas líneas de código (casi ninguna) podemos no  sólo tratar la respuesta del servidor, sino al igual que hemos hecho antes, rellenar un TClientDataset con los datos recibidos.

Para la petición y la respuesta utilizaremos los componentes REST que he comentado:

  •     RESTRequest1: TRESTRequest;
  •     RESTClient1: TRESTClient;
  •     RESTResponse1: TRESTResponse;

Y para convertir los datos, tenemos otro componente llamado “TRESTResponseDataSetAdapter”, que como bien dice su nombre nos hace de adaptador entre la respuesta (en este caso en formato JSON) y un Dataset (TClientDataset).

La forma de utilizar los tres primeros componentes es sencilla.

Imagen10

El componente TRestRequest posee la propiedad Client y la propiedad Response, que se utilizan para asignar los otros 2 componentes.
Si vemos en contenido del DFM (para estos tres componentes) veremos que no tienen nada más que configurar:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  object RESTRequest1: TRESTRequest
    Client = RESTClient1
    Params = <>
    Response = RESTResponse1
    OnAfterExecute = RESTRequest1AfterExecute
    Left = 24
    Top = 440
  end
  object RESTClient1: TRESTClient
    Accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
    Params = <>
    HandleRedirects = True
    Left = 80
    Top = 440
  end
  object RESTResponse1: TRESTResponse
    Left = 136
    Top = 440
  end

Sólo nos queda realizar la búsqueda utilizando TRestClient según los parámetros. Para ello yo he creado 2 procedimientos simples:

0
1
    procedure SearchUsuarios(AId:integer); overload;
    procedure SearchUsuarios(ANombre:string); overload;

Cuya implementación es muy básica:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
procedure TFMain.SearchUsuarios(AId: integer);
var
  sURL:string;
begin
  sURL := URL_BASE + '?user=' + IntToStr(AId);
  RESTClient1.BaseURL := sURL;
  // Busqueda
  RESTRequest1.Execute;
end;
 
procedure TFMain.SearchUsuarios(ANombre: string);
var
  sURL:string;
begin
  sURL := URL_BASE + '?nom=' + ANombre;
  RESTClient1.BaseURL := sURL;
  // Busqueda
  RESTRequest1.Execute;
end;

Como veis, lo único que hacemos es añadir el parámetros necesario a la URL_BASE (dirección de nuestro WebService) y lanzar la petición.
Una vez que la petición se completa, la respuesta llega a nuestro componente TRestResponseAdapter, que “une” nuestro componente de respuesta (TRestResponse) con el componente Dataset (TClientDataSet). Si Hemos realizado la definición de campos de forma correcta, tendremos automáticamente los registros que devuelve la petición en nuestro TClientDataset (facil y límpio).

Imagen11

Para este ejemplo, he modificado el fichero del Webservice (el nuevo está en listadoREST.php) para que los parámetros sean más fáciles de recoger con el componentes TRESTResponseDatasetAdapter, y además lo he ampliado (en el fichero listadoREST2.php) con una búsqueda (un nuevo parámetro) por el campo nombre. Para ello se tiene en cuenta un nuevo parámetro nom, y se realiza un LIKE sobre la tabla de usuarios, utilizando caracteres comodín.

Aquí podéis ver un pequeño vídeo del proyecto en el IDE y del mismo funcionando.

 

CLIENTE 3 (Delphi XE5 + Componentes SOAP)

 

wsdl3Como tercer ejemplo voy a realizar una aplicación, utilizando los componentes SOAP. En este caso necesito importar el fichero WSDL y generar la unit que me permitirá acceder a los métodos creados en el Servidor de forma sencilla. En la entrada en que generamos nuestro servidor, vimos al final una variante utilizando la librería nuSOAP, para poder generar de forma automática este fichero. Este fichero será el que utilizaremos para importarlo desde Delphi.

 

Para importar el fichero WSDL lo podemos realizar desde Delphi utilizando el asistente “Import WSDL”; Se encuentra en el menú File/New/Other en la pestaña de WebServices.

Aquí podéis ver dos imágenes del proceso de Importación. Imagen12 Una vez que tenemos la unit generada queda añadida a nuestro proyecto. Podemos ver, ya en el momento de la generación las estructuras complejas definidas para los datos y los métodos definidos en nuestro WebService.

05t5

 

Para realizar la comunicación vamos a utilizar el componente THTTPRIO. Configuramos la dirección del fichero WSDL y las propiedades Port y Service que nos ofrece el componente.

 

Imagen13

 

Definimos un array  de objetos (utilizando las clases definidas en el WebService) para recibir la lista de usuarios y realizamos la llamada utilizando algunos de los métodos definidos: ConsultaUsuario o ConsultaUsuarios.

 

0
1
2
3
4
5
6
7
8
9
10
11
12
13
var
  arr:ArregloDeEstructuras;
  info:Estructura;
  l, i:Integer;
begin
  // Según el rellenado llamamos a uno u otro
  if (edtId.Text <> '') then begin
    arr := (HTTPRIO1 as Consulta_de_usuariosPortType).ConsultaUsuario(
      StrToInt(edtId.text));
  end
  else begin
    arr := (HTTPRIO1 as Consulta_de_usuariosPortType).ConsultaUsuarios(
      edtNombre.text);
  end;

Una vez obtenemos la respuesta, lo único que tenemos que hacer es recorrer el array, para tener todos los usuarios que nos ha devuelto la consulta.

ejemplo

Espero que os haya sido útil e interesante. Como los comentarios, sugerencias, críticas  y demás, serán bienvenidas.
Os adjunto los links para descargar los tres proyectos (código fuente y ejecutables):

Pues hasta aquí hemos llegado hoy.

Hasta la próxima.

Share Button
  1. kumo
    viernes, 24 de marzo de 2017 a las 23:34 | #1

    hola, compañero tengo una trama Json en archivo *.txt* y nececito parcearla.
    el método principal lo tengo así y funciona:
    procedure TForm1.trama;
    var
    fRespuesta: String;
    obj: TlkJSONbase;
    v_ : Integer;
    begin
    mmo1.Lines.LoadFromFile(‘D:\SL_SOFTWARE\LaboratorioBACKUP_DBLAB_HRD25_1.DL3\leerJSON\JSON_ORDEN.txt’);
    fRespuesta:= mmo1.Lines.Text;
    obj:= TlkJSONbase.Create();
    try
    mmo1.Clear;
    v_:=0;
    obj:= TlkJSON.ParseText(fRespuesta);
    mmo1.Lines.Text:= GenerateReadableText(obj, v_);
    datosEnTabla(TlkJSONobject(obj));
    finally
    FreeAndNil(obj);
    end;
    end;

    la trama que necesito parsear en tablas temporales son:
    {
    “cabeza1”: {
    “tabla1”: {
    “campo”: “0”,
    “campo”: “1”
    },
    “tabla2”: {
    “campo”: “xxxxxx”,
    “campo”: “xxxxxx”,
    “campo”: “xxxxxxx”
    },
    “tabla3”: {
    “campo”: “L-xxxxxxxxxxxx”
    },
    “tabla4”: {
    “tabla4.1”: {
    “campo”: “xxxxxxxxxxxx”,
    “campo”: “xxxxx”,
    “campo”: “x”,
    “campo”: “”
    }
    }
    }
    para un solo registro debo interpretar dicha trama, ahora bien, en la linea
    **datosEnTabla(TlkJSONobject(obj));** la ejecucion se revienta.

  2. kumo
    viernes, 24 de marzo de 2017 a las 23:40 | #2

    y el codigo que tengo en dicha funcion es:
    procedure TForm1.datosEnTabla(AObjResp: TlkJSONobject);
    var
    objDatosList:TlkJSONlist;
    objDatos, objtmp:TlkJSONobject;
    objBase: TlkJSONbase;
    i,j,k,l:integer;
    Str:String;
    cds1,cds2,cds3,cds4:TClientDataSet;
    begin
    // Limpiar el contenido
    cds1 := cd1;
    cds1.EmptyDataSet;
    cds2:= cd2;
    cds2.EmptyDataSet;
    cds3:= cd3;
    cds3.EmptyDataSet;
    cds4:= cd4;
    cds4.EmptyDataSet;
    // Objeto usuarios?
    *** if (AObjResp.IndexOfName(‘DocumentElement’) -1) then ***
    begin
    //(AObjResp.IndexOfName(‘DatosGenerales’) -1)
    // Lo almaceamos en un tipo “lista”
    TlkJSONBase(objDatosList) := AObjResp.Field[‘DocumentElement’];
    // Recorrer la lista de usuarios.
    for i := 0 to (objDatosList.Count – 1) do begin
    // Objeto usuario
    TlkJSONBase(objDatos) := objDatosList.Child[i];
    TlkJSONBase(objtmp) := objDatos.FieldByIndex[0];
    // Añadimos un registro al CDS
    cds1.Append;
    try
    // Recoger los campos del objeto
    cds1.FieldByName(‘xxxx’).AsString := VarToStr(objtmp.Field[‘xxx’].Value);
    cds1.FieldByName(‘xxxxx’).AsString := VarToStr(objtmp.Field[‘xxx’].Value);
    cds1.FieldByName(‘xx’).AsString := VarToStr(objtmp.Field[‘xxx’].Value);
    cds1.FieldByName(‘xxx’).AsString := VarToStr(objtmp.Field[‘xxx’].Value);
    // mostrar
    cds1.Open;
    finally
    cds1.Cancel;
    end;
    end;
    //TlkJSONBase(objDatosList) := AObjResp.Field[‘DatosPaciente’];
    for j := 0 to (objDatosList.Count – 1) do begin
    // Objeto usuario
    TlkJSONBase(objDatos) := objDatosList.Child[j];
    TlkJSONBase(objtmp) := objDatos.FieldByIndex[0];
    // Añadimos un registro al CDS
    cds2.Append;
    try
    // Recoger los campos del objeto
    cds2.FieldByName(‘xxx’).AsString := VarToStr(objtmp.Field[‘xxx’].Value);
    cds2.FieldByName(‘xxx’).AsString := VarToStr(objtmp.Field[‘xxx’].Value);
    cds2.FieldByName(‘xxx’).AsString := VarToStr(objtmp.Field[‘xx’].Value);
    cds2.FieldByName(‘xxx’).AsString := VarToStr(objtmp.Field[‘xx’].Value);
    // mostrar
    cds2.Open;
    finally
    cds2.Cancel;
    end;
    end;
    //TlkJSONBase(objDatosList) := AObjResp.Field[‘DatosOrden’];
    for k := 0 to (objDatosList.Count – 1) do begin
    // Objeto usuario
    TlkJSONBase(objDatos) := objDatosList.Child[k];
    TlkJSONBase(objtmp) := objDatos.FieldByIndex[0];
    // Añadimos un registro al CDS
    cds3.Append;
    try
    // Recoger los campos del objeto
    cds3.FieldByName(‘xxxxx’).AsString := VarToStr(objtmp.Field[‘xxxx’].Value);
    // mostrar
    cds3.Open;
    finally
    cds3.Cancel;
    end;
    end;
    //TlkJSONBase(objDatosList) := AObjResp.Field[‘Examen’];
    for l := 0 to (objDatosList.Count – 1) do begin
    // Objeto usuario
    TlkJSONBase(objDatos) := objDatosList.Child[l];
    TlkJSONBase(objtmp) := objDatos.FieldByIndex[0];
    // Añadimos un registro al CDS
    cds4.Append;
    try
    // Recoger los campos del objeto
    cds4.FieldByName(‘xx’).AsString := VarToStr(objtmp.Field[‘xx’].Value);
    cds4.FieldByName(‘xx’).AsString := VarToStr(objtmp.Field[‘xx’].Value);
    cds4.FieldByName(‘xx’).AsString := VarToStr(objtmp.Field[‘xx’].Value);
    cds4.FieldByName(‘xx’).AsString := VarToStr(objtmp.Field[‘xx’].Value);
    cds4.FieldByName(‘xx’).AsString := VarToStr(objtmp.Field[‘xx’].Value);
    // mostrar
    cds4.Open;
    finally
    cds4.Cancel;
    end;
    end;
    end;
    end;

    en la linea demarcada por los 3 asteriscos me arroja el error de inmediato… que puedo estar aplicando mal?
    GRACIAS POR LA AYUDA.

  3. kumo
    viernes, 24 de marzo de 2017 a las 23:48 | #3

    vale aclarar que cabeza1=documentElement, tabla1=DatosGenerales, tabla2=DatosPaciente, tabla3=DatosOrden, tabla4=Examenes, tabla4.1=Examen.

Comment pages
1 2 3335
  1. Sin trackbacks aún.
What is 14 + 28 ?
Please leave these two fields as-is:
IMPORTANTE! Para continuar, debes contestar la pregunta anterior (para evitar SPAM) :-)