Capítulo 2. Crear el Web ServiceRegresar al índice
En este capítulo veremos como crear nuestro
Web Service implementando la conectividad con la base de datos utilizando un
SOAP Server Data Module, agregaremos componentes
dbExpress para el acceso y escritura de los datos, utilizaremos la clase
TRemotable (algo nuevo para mi y creo que para varios de nosotros) para la recepción de algunos parámetros desde nuestra aplicación cliente y escribiremos las funciones para acceder a la base de datos que serán consumidas desde la aplicación Cliente.
Nota: He decidido usar dbExpress debido a que se comenta que en la nueva versión de Delphi se pretende tener soporte a Firebird.En los siguientes capítulos les mostrará a detalle cada uno de estos pasos.
Capítulo 2.1 Creamos un Nuevo ProyectoRegresar al índice
Comenzaremos por crear un nuevo proyecto desde el menú de Delphi:
File --> New --> Other --> WebService --> SOAP Server ApplicationSeleccionamos el Tipo
ISAPI para la Aplicación del Servidor Web la cual nos generá una
DLL al compilar nuestro
Web Service.
Aceptamos crear la interfaz del
Módulo SOAP.
Y finalmente agregamos un nuevo
Web Service al que nombraremos
WSdbAccess con el modelo de activacion del Servicio
Per Request que significa que cada petición entrante se creará una nueva instancia del objeto SOAP (y destruida después de ser usada).
Una vez completado estos pasos, Delphi nos generará la estructura básica de nuestro
Web Service WSdbAccess.bdsprojlibrary WSdbAccess;
uses
ActiveX,
ComObj,
WebBroker,
ISAPIApp,
ISAPIThreadPool,
uWSdbAccess in 'uWSdbAccess.pas' {WebModule2: TWebModule},
WSdbAccessImpl in 'WSdbAccessImpl.pas',
WSdbAccessIntf in 'WSdbAccessIntf.pas';
{$R *.res}
exports
GetExtensionVersion,
HttpExtensionProc,
TerminateExtension;
begin
CoInitFlags := COINIT_MULTITHREADED;
Application.Initialize;
Application.CreateForm(TWebModule2, WebModule2);
Application.Run;
end.
uWSdbAccess.pasunit uWSdbAccess;
interface
uses
SysUtils, Classes, HTTPApp, InvokeRegistry, WSDLIntf, TypInfo, WebServExp,
WSDLBind, XMLSchema, WSDLPub, SOAPPasInv, SOAPHTTPPasInv, SOAPHTTPDisp,
WebBrokerSOAP;
type
TWebModule2 = class(TWebModule)
HTTPSoapDispatcher1: THTTPSoapDispatcher;
HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker;
WSDLHTMLPublish1: TWSDLHTMLPublish;
procedure WebModule2DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;
var
WebModule2: TWebModule2;
implementation
{$R *.dfm}
procedure TWebModule2.WebModule2DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
WSDLHTMLPublish1.ServiceInfo(Sender, Request, Response, Handled);
end;
end.
WSdbAccessImpl.pas{ Invokable implementation File for TWSdbAccess which implements IWSdbAccess }
unit WSdbAccessImpl;
interface
uses InvokeRegistry, Types, XSBuiltIns, WSdbAccessIntf;
type
{ TWSdbAccess }
TWSdbAccess = class(TInvokableClass, IWSdbAccess)
public
end;
implementation
initialization
{ Invokable classes must be registered }
InvRegistry.RegisterInvokableClass(TWSdbAccess);
end.
WSdbAccessIntf.pas{ Invokable interface IWSdbAccess }
unit WSdbAccessIntf;
interface
uses InvokeRegistry, Types, XSBuiltIns;
type
{ Invokable interfaces must derive from IInvokable }
IWSdbAccess = interface(IInvokable)
['{7923453C-068B-4CE6-A012-D6C3AC837B8E}']
{ Methods of Invokable interface must not use the default }
{ calling convention; stdcall is recommended }
end;
implementation
initialization
{ Invokable interfaces must be registered }
InvRegistry.RegisterInterface(TypeInfo(IWSdbAccess));
end.
En este momento ya podemos comenzar a agregar todo lo necesario para desarrollar nuestro
Web Service.
Capítulo 2.2 Creamos la Conectividad con la Base de DatosRegresar al índice
Para crear la conectividad con la base de datos usaremos un
Módulo de Datos SOAP el cual se agregará desde el menú de Delphi:
File --> New --> Other --> WebService --> SOAP Server Data ModuleAsignamos
frmDataModule al nombre del Módulo de Datos.
En este momento veremos el típico Módulo de Datos pero con la estructura invocable requerida. Guardamos nuestra unidad como
uDataModule.
Ahora vamos a crear una nueva conexión a nuestra base de datos que como ya he mencionado usaremos
dbExpress pensando en que Firebird está soportado en la siguiente versión de Delphi.
Abrimos el
Data Explorer de Delphi (recuerden que estoy usando Turbo Delphi) y realizamos lo siguiente:
INTERBASE --> dbExpress --> botón derecho del mouse Add New Connection --> Provider Name = INTERBASE, Connection Name = CLIENTESPosteriormente editamos las propiedades de la conexión y configuramos las propiedades
DataBase y
HostName,
DecimalSeparator y
PrepareSQL como se muestra en la siguiente image, la ubicación de la base de datos depende donde la han colocado ustedes.
Ya tenemos nuestra conexión, la seleccionamos y la arrastramos al
Módulo de Datos.
Ahora vamos a agregar 5
SQLDataSet para escribir las sentencias
SQL correspondientes al acceso de datos, he decidido hacerlo por separado para ser mas claro en los procesos.
Cambiamos el nombre de cada uno de los
SQLDataSet de acuerdo al siguiente esquema:
dsClientList: Nos regresará una lista de los clientes únicamente con los Campos CLIENTE_ID, NOMBRE y APELLIDOS
SELECT CLIENTE_ID, NOMBRE, APELLIDOS FROM CLIENTES
dsClientData:Nos regresará el detalle del cliente para ser mostrado.
SELECT * FROM CLIENTES WHERE CLIENTE_ID = :ID
dsAddClient:Nos permitirá agregar registros a la tabla CLIENTES
INSERT INTO CLIENTES
(CLIENTE_ID, TITULO_CLIENTE, NOMBRE, APELLIDOS, FECHA_NACIMIENTO, DIRECCION, CIUDAD, PAIS,
CODIGO_POSTAL, TELEFONO_CASA, TELEFONO_CELULAR, TELEFONO_TRABAJO, CORREO_ELECTRONICO)
VALUES
(NULL, :TITULO_CLIENTE, :NOMBRE, :APELLIDOS, :FECHA_NACIMIENTO, :DIRECCION, :CIUDAD,
:PAIS, :CODIGO_POSTAL, :TELEFONO_CASA, :TELEFONO_CELULAR, :TELEFONO_TRABAJO,
:CORREO_ELECTRONICO)
dsDeleteClient:Nos permitirá borrar un registro de la tabla CLIENTES
DELETE FROM CLIENTES WHERE CLIENTE_ID = :ID
dsUpdateClient:Nos permitirá modificar el detalle de los clientes.
UPDATE CLIENTES
SET
TITULO_CLIENTE = :TITULO_CLIENTE,
NOMBRE = :NOMBRE,
APELLIDOS = :APELLIDOS,
FECHA_NACIMIENTO = :FECHA_NACIMIENTO,
DIRECCION = :DIRECCION,
CIUDAD = :CIUDAD,
PAIS = :PAIS,
CODIGO_POSTAL = :CODIGO_POSTAL,
TELEFONO_CASA = :TELEFONO_CASA,
TELEFONO_CELULAR = :TELEFONO_CELULAR,
TELEFONO_TRABAJO = :TELEFONO_TRABAJO,
CORREO_ELECTRONICO = :CORREO_ELECTRONICO
WHERE
CLIENTE_ID = :ID
Ya estamos listos para comenzar a codificar nuestro
Web Service.
Capítulo 2.3 La Clase TRemotableRegresar al índice
Antes de comenzar a escribir código les voy a mostrar una clase especial que nos será de mucha utilidad para nuestros procesos.
TRemotable es la
Clase Base para las clases que se pueden pasar como parámetros o valores de retorno a una solicitud en un servicio Web, dicho en otras palabras, esta clase nos permitirá transportar desde y hacia nuestra Aplicación Cliente un conjunto de elementos que no sería posible si pretendemos trasportarlos usando el tipo
record tradicional.
Vamos a crear dos clases
trParams y
trRetorno de tipo
TRemotable en la unidad
WSdbAccessIntf.pas. La primera nos servirá para recibir los parámetros que serán ingresados y/o actualizados a la tabla CLIENTES y la segunda nos servirá para regresar a la aplicación Cliente el resultado de las transacciones.
A continuación mostrará la estructura de cada una de las clases.
type
trRetorno = class(TRemotable)
private
FID: integer;
FDescripcion: widestring;
published
property ID: integer read FID write FID;
property Descripcion: widestring read FDescripcion write FDescripcion;
end;
trParams = class(TRemotable)
private
FTitulo: widestring;
FNombre: widestring;
FApellidos: widestring;
FFecha: widestring;
FDireccion: widestring;
FCiudad: wideString;
FPais: wideString;
FCodPostal: wideString;
FTelCasa: wideString;
FTelOficina: wideString;
FCelular: wideString;
FCorreo: wideString;
published
property Titulo: widestring read FTitulo write FTitulo;
property Nombre: widestring read FNombre write FNombre;
property Apellidos: widestring read FApellidos write FApellidos;
property Fecha: widestring read FFecha write FFecha;
property Direccion: widestring read FDireccion write FDireccion;
property Ciudad: widestring read FCiudad write FCiudad;
property Pais: widestring read FPais write FPais;
property CodPostal: widestring read FCodPostal write FCodPostal;
property TelCasa: widestring read FTelCasa write FTelCasa;
property TelOficina: widestring read FTelOficina write FTelOficina;
property Celular: widestring read FCelular write FCelular;
property Correo: widestring read FCorreo write FCorreo;
end;
{ Invokable interfaces must derive from IInvokable }
IWSdbAccess = interface(IInvokable)
['{7923453C-068B-4CE6-A012-D6C3AC837B8E}']
{ Methods of Invokable interface must not use the default }
{ calling convention; stdcall is recommended }
end;
Con esto ya tenemos la base para escribir nuestro código lo cual veremos en el siguiente capítulo.
Capítulo 2.4 Creando las funciones del ABMRegresar al índice
Ya estamos listos para escribir nuestro código, comenzaremos por "diseñar" 5 funciones en la unidad
WSdbAccessIntf.pas dentro de la interfaz invocable
IWSdbAccess.
{ Invokable interfaces must derive from IInvokable }
IWSdbAccess = interface(IInvokable)
['{7923453C-068B-4CE6-A012-D6C3AC837B8E}']
function GetClientNames: widestring; stdcall;
function GetClientsData(EmpID: integer): widestring; stdcall;
function AddClient(ParamsStr:trParams): trRetorno; stdcall;
function DeleteClient(EmpID: integer): trRetorno; stdcall;
function UpdateClient(EmpID:integer; ParamsStr:trParams): trRetorno; stdcall;
{ Methods of Invokable interface must not use the default }
{ calling convention; stdcall is recommended }
end;
Ahora, debemos copiar estas funciones a la unidad
WSdbAccessImpl.pas en la clase
TWSdbAccess donde haremos la implementación de dichas funciones.
{ Invokable implementation File for TWSdbAccess which implements IWSdbAccess }
unit WSdbAccessImpl;
interface
uses InvokeRegistry, Types, XSBuiltIns, WSdbAccessIntf;
type
{ TWSdbAccess }
TWSdbAccess = class(TInvokableClass, IWSdbAccess)
public
function GetClientNames: widestring; stdcall;
function GetClientsData(EmpID: integer): widestring; stdcall;
function AddClient(ParamsStr:trParams): trRetorno; stdcall;
function DeleteClient(EmpID: integer): trRetorno; stdcall;
function UpdateClient(EmpID:integer; ParamsStr:trParams): trRetorno; stdcall;
end;
implementation
initialization
{ Invokable classes must be registered }
InvRegistry.RegisterInvokableClass(TWSdbAccess);
end.
Nos posicionamos en cualquiera de estas funciones y presionamos
Ctrl + Shift + C, Delphi nos generará las funciones en la sección de implementación automáticamente.
implementation
{ TWSdbAccess }
function TWSdbAccess.GetClientNames: widestring;
begin
end;
function TWSdbAccess.GetClientsData(EmpID: string): widestring;
begin
end;
function TWSdbAccess.AddClient(ParamsStr:trParams): trRetorno;
begin
end;
function TWSdbAccess.DeleteClient(EmpID: integer): trRetorno; stdcall;
begin
end;
function TWSdbAccess.UpdateClient(EmpID:integer; ParamsStr:trParams): trRetorno; stdcall;
begin
end;
Antes de comenzar a codificar nuestras funciones crearemos dos funciones que generarán la estructura
XML de los campos de los
DataSet, dichas funciones por lo que estuve investigando son autoría de
Marco Cantú si alguien tiene otra referencia me gustaría que lo hicieran notar para dar crédito a quien lo merece.
function MakeXmlStr (node, value: string): string;
begin
Result := '<' + node + '>' + value + '</' + node + '>';
end;
function FieldsToXml (rootName: string; data: TSQLDataSet): string;
var
i: Integer;
begin
Result := '<' + rootName + '>' + sLineBreak;;
for i := 0 to data.FieldCount - 1 do
Result := Result + ' ' + MakeXmlStr (
LowerCase (data.Fields[i].FieldName),
data.Fields[i].AsString) + sLineBreak;
Result := Result + '</' + rootName + '>' + sLineBreak;;
end;
Ahora agregamos las unidades
uDataModule,
sysUtils y
sqlExpr además de una variable de tipo
TfrmDataModule.
implementation
uses uDataModule, sysUtils, sqlExpr;
var
dm: TfrmDataModule;
Ahora si, comencemos con la implementación de nuestras funciones.
Función GetClientNames: widestring;Esta función nos regresará un listado de los clientes que se tengan en la base de datos en formato XML dentro de una variable
widestring.
function TWSdbAccess.GetClientNames: widestring;
begin
dm := TfrmDataModule.Create (nil);
try
dm.dsClientList.Open;
if dm.dsClientList.RecordCount > 0 then begin
Result := FieldsToXml ('ClientList', dm.dsClientList);
end
else begin
Result := '<ClientList>' + sLineBreak +
'</ClientList>' + sLineBreak;
end;
finally
dm.dsClientList.Close;
dm.CLIENTES.Close;
dm.Free;
end;
end;
Este código creará una estructura
XML con el listado de clientes con el siguiente formato:
<ClientList>
<cliente_id>1</cliente_id>
<nombre>JUAN</nombre>
<apellidos>PEREZ</apellidos>
<cliente_id>2</cliente_id>
<nombre>REMEDIOS</nombre>
<apellidos>CASEROS</apellidos>
<cliente_id>3</cliente_id>
<nombre>ALMA MARIA</nombre>
<apellidos>RICO</apellidos>
</ClientList>
Función GetClientsData(EmpID: integer): widestring;Esta función nos regresará el detalle del cliente solicitado en el parámetro
EmpID en formato XML dentro de una variable
widestring.
function TWSdbAccess.GetClientsData(EmpID: integer): widestring;
begin
dm := TfrmDataModule.Create (nil);
try
dm.dsClientData.Params.ParamByName('ID').Value := EmpID;
dm.dsClientData.Open;
Result := FieldsToXml ('ClientData', dm.dsClientData);
finally
dm.dsClientData.Close;
dm.CLIENTES.Close;
dm.Free;
end;
end;
Este código creará una estructura
XML con el detalle del cliente solicitado con el siguiente formato:
<Clientes>
<cliente_id>2</cliente_id>
<titulo_cliente>SRA.</titulo_cliente>
<nombre>REMEDIOS</nombre>
<apellidos>CASEROS</apellidos>
<fecha_nacimiento>01/11/1956</fecha_nacimiento>
<direccion>CALLE DE LA AMARGURA 69</direccion>
<ciudad>DE LA INFLUENZA</ciudad>
<pais>TUMBUCTU</pais>
<codigo_postal>12345</codigo_postal>
<telefono_casa>111-111-1111</telefono_casa>
<telefono_trabajo>111-212-1122</telefono_trabajo>
<telefono_celular>222-123-1234</telefono_celular>
<correo_electronico>remedios.caseros@gmail.com</correo_electronico>
</Clientes>
function AddClient(ParamsStr: trParams): trRetorno;Esta función agregará el registro del cliente con los parámetros recibidos en la clase
trParams y nos regresará un aviso para determinar si la transacción fue satisfactoria o no en la clase
trRetornofunction TWSdbAccess.AddClient(ParamsStr: trParams): trRetorno;
begin
Result := trRetorno.Create;
dm := TfrmDataModule.Create(nil);
try
with dm.dsAddClient do begin
ParamByName('TITULO_CLIENTE').value := ParamsStr.Titulo;
ParamByName('NOMBRE').value := ParamsStr.Nombre;
ParamByName('APELLIDOS').value := ParamsStr.Apellidos;
ParamByName('FECHA_NACIMIENTO').value := strtodate(ParamsStr.Fecha);
ParamByName('DIRECCION').Value := ParamsStr.Direccion;
ParamByName('CIUDAD').Value := ParamsStr.Ciudad;
ParamByName('PAIS').Value := ParamsStr.Pais;
ParamByName('CODIGO_POSTAL').Value := ParamsStr.CodPostal;
ParamByName('TELEFONO_CASA').Value := ParamsStr.TelCasa;
ParamByName('TELEFONO_TRABAJO').Value := ParamsStr.TelOficina;
ParamByName('TELEFONO_CELULAR').Value := ParamsStr.Celular;
ParamByName('CORREO_ELECTRONICO').Value := ParamsStr.Correo;
end;
dm.dsAddClient.ExecSQL();
Result.ID := 0;
Result.Descripcion := 'El cliente fuá agregado correctamente';
except
Result.ID := -1;
Result.Descripcion := 'Error al agregar un nuevo Cliente';
end;
dm.CLIENTES.Close;
dm.Free;
end;
Función DeleteClient(EmpID: integer): trRetorno;En esta función solo recibiremos el identificador del cliente que se desea borrar y la funcion retornará un aviso si fue satisfactoria o no la transacción dentro de la clase
trRetorno.
function TWSdbAccess.DeleteClient(EmpID: integer): trRetorno;
begin
Result := trRetorno.Create;
dm := TfrmDataModule.Create(nil);
try
dm.dsDeleteClient.paramByName('ID').Value := EmpID;
dm.dsDeleteClient.ExecSQL();
Result.ID := 0;
Result.Descripcion := 'El cliente solicitado fué borrado correctamente';
except
Result.ID := -1;
Result.Descripcion := 'Error al borrar el cliente solicitado';
end;
dm.CLIENTES.Close;
dm.Free;
end;
Función UpdateClient(EmpID: integer; ParamsStr: trParams): trRetorno;En esta función recibiremos el identificador del cliente que deseamos modificar/actualizar y los parámetros con los cuales se modificará el registro dentro de la clase
trParams y regresará si la transacción fuá satisfactoria o no dentro de la clase
trRetorno.
function TWSdbAccess.UpdateClient(EmpID: integer; ParamsStr: trParams): trRetorno;
begin
Result := trRetorno.Create;
dm := TfrmDataModule.Create(nil);
try
with dm.dsUpdateClient do begin
ParamByName('TITULO_CLIENTE').value := ParamsStr.Titulo;
ParamByName('NOMBRE').value := ParamsStr.Nombre;
ParamByName('APELLIDOS').value := ParamsStr.Apellidos;
ParamByName('FECHA_NACIMIENTO').value := strtodate(ParamsStr.Fecha);
ParamByName('DIRECCION').Value := ParamsStr.Direccion;
ParamByName('CIUDAD').Value := ParamsStr.Ciudad;
ParamByName('PAIS').Value := ParamsStr.Pais;
ParamByName('CODIGO_POSTAL').Value := ParamsStr.CodPostal;
ParamByName('TELEFONO_CASA').Value := ParamsStr.TelCasa;
ParamByName('TELEFONO_TRABAJO').Value := ParamsStr.TelOficina;
ParamByName('TELEFONO_CELULAR').Value := ParamsStr.Celular;
ParamByName('CORREO_ELECTRONICO').Value := ParamsStr.Correo;
ParamByName('ID').Value := EmpID;
end;
dm.dsUpdateClient.ExecSQL();
Result.ID := 0;
Result.Descripcion := 'El cliente fuá modificado correctamente';
except
Result.ID := -1;
Result.Descripcion := 'Error al modificar el Cliente';
end;
dm.CLIENTES.Close;
dm.Free;
end;
Ahora solo nos resta generar el código para que nuestro Web Service pueda obtener los parámetros de conexión a una base de datos en tiempo de ejecución leyendo un archivo INI.
Para realizar esto usaremos el evento OnCreate del
frmDataModule para realizar dicha lectura. Debemos agregar las unidades
IniFiles y
windows en el uses.
procedure TfrmDataModule.SoapDataModuleCreate(Sender: TObject);
var
IniFile: TIniFile;
DATABASE,SERVER: string;
S: array[0..MAX_PATH] of Char;
begin
windows.GetModuleFileName(hInstance, S, SizeOf(S));
IniFile := TIniFile.Create(ExtractFileDir(S)+'\params.ini');
DATABASE := IniFile.ReadString('PARAMETERS','Path','');
SERVER := IniFile.ReadString('PARAMETERS','Host','');
IniFile.Free;
Clientes.Params.Values['HOSTNAME'] := SERVER;
Clientes.Params.Values['DATABASE'] := SERVER+':'+DATABASE;
end;
El archivo
params.ini debe estár en el mismo directorio de nuestro Web Service y deberá contener lo siguiente:
[PARAMETERS]
path="DRIVE:\RUTA\DBCLIENTES.FDB"
host="LOCALHOST"
Con esto hemos terminado nuestro Web Service, compilamos y si todo está correctamente nos creará el archivo
WSdbAccess.dll el cual debemos registrar en el IIS, el proceso de registrar el
Web Service lo veremos en el siguiente capítulo.