Servicios Web (I): Xojo Web a tu servicio (API 2.0)

Utilizar Xojo Web para la creación de aplicaciones y soluciones web no sólo significa que no tendrás que aprender una buena cantidad de lenguajes interpretados y decenas de frameworks cambiantes. Por supuesto, Xojo Web no sólo te permite crear tus propias aplicaciones web sino que también puedes utilizarlo como el middleware para que tus aplicaciones Desktop e iOS se puedan comunicar con una base de datos remota. Aprende como puedes crear tus propias APIs y servicios web con Xojo mediante este tutorial.

En este tutorial en dos partes verás lo fácil que resulta crear un servicio web básico con Xojo Web. En la segunda parte crearemos un cliente Desktop que se comunicará con el servicio web (¡también puedes añadir iOS a la lista!).

Antes de comenzar, este tutorial deja fuera los detalles relacionados con la comprobación de errores, validación y saneamiento de datos así como otros aspectos relaciones con las entradas y salida de datos, de modo que nos centremos en el tema principal. Además, si quieres seguir y reproducir los pasos de este tutorial, entonces necesitarás descargar la base de datos Chinook Database, .

Servicios en la práctica

¡Comencemos a crear nuestro servicio Web! Abre el IDE de Xojo, crea un nuevo proyecto Web, y nómbralo como WebService. Lo primero que observarás es que Xojo añade una página web por defecto al proyecto, incluso teniendo en cuenta que nuestro servicio web no mostrará ninguna interfaz de usuario. Lo bueno es que puedes combinar ambos mundos, añadiendo el servicio web como parte de una aplicación web convencional, si así lo necesitas.

Este tutorial pondrá toda la lógica en el objeto App. Empieza añadiendo una nueva propiedad que se encargará de referenciar nuestra base de datos SQLite (por supuesto, también puedes adaptarlo para que funcione con PostgreSQL o MySQL). Selecciona el objeto App en el Navegador (el panel situado a la izquierda en el IDE de Xojo), seleccionando la opción Insert > Property en el menú contextual. Con la nueva propiedad aún seleccionada, dirígete al Inspector  para definir el nombre, tipo y ámbito utilizando los siguientes valores:

  • Name: Database
  • Type: SQLiteDatabase
  • Scope: Private

A continuación añadiremos la base de datos Chinook a la carpeta Resources de la aplicación Web. Para ello, selecciona el sistema operativo de despliegue en la sección Build Settings (puede ser macOS, Windows, Linux o Xojo Cloud), y añade un paso Copy Files. En el Editor resultante, añade la base de datos SQLite Chinook y asegúrate de seleccionar la entrada “Resources Folder” en el menú desplegable Destination, así como la entrada “Both” en el menú desplegable Applies To.

Ahora es momento de crear una nueva instancia de SQLiteDatabase y asignarla a esta propiedad, de modo que apunte al archivo de base de datos cuando se esté ejecutando la aplicación. Para ello, asegúrate de que esté seleccionado el objeto App y añade el evento Opening usando la opción Insert > Event. Escribe el siguiente código en el Editor de Código resultante:

#Pragma Unused args

Var f As FolderItem = SpecialFolder.Resource("Chinook_Sqlite.sqlite")

If f.Exists Then
  
  Try
    database = New SQLiteDatabase
    database.DatabaseFile = f
    
    Call database.Connect
  Catch e As DatabaseException
    MessageBox "Error connecting to the database"
  End Try
  
End If

Como puedes ver es básicamente el mismo código que utilizamos cuando creamos una instancia de base de datos SQLiteDatabase en nuestras aplicaciones Desktop, enlazándola con el archivo de base de datos SQLite y estableciendo la conexión de modo que podamos utilizar desde nuestra aplicación.

Toda la Magia de HandleURL

Los proyectos Xojo Web ofrecen un modo realmente sencillo de manejar las peticiones recibidas. Se hace a través del evento HandleURL. Este es el que se invoca cada vez que la aplicación cliente (puede ser un navegador web, una aplicación móvil o de escritorio) se conecta con el URL asociado con la combinación de dirección IP y puerto en la que se encuentra escuchando las peticiones entrantes la aplicación web.

Por ejemplo, este sería un URL válido que activa el evento HandleURL:

http://www.nice-web-domain.com/getCustomers

Donde getCustomers se corresponde en este caso con uno de los métodos de nuestra API.

De modo que, con el objeto App seleccionado, selecciona la opción Insert > Event para añadir el evento HandleURL.

Como veremos, una vez que se ha añadido el evento a la aplicación Web, este recibirá el parámetro Request (se trata de un tipo de dato WebRequest), y esperará a que devolvamos un valor booleano como respuesta: True en el caso de que nuestra aplicación haya procesado la petición, o bien False para ignorar la respuesta.

Presentamos Request, donde reside la información

De hecho encontraremos en el objeto Request todo lo necesario para procesar y (si es el caso) responder a la petición desde nuestro servicio Web. Por ejemplo, podemos obtener el componente que nos interesa a través de la propiedad Path. Si consideramos el siguiente URL:

http://www.nice-web-domain.com/getCustomers?spain

La propiedad Request.Path nos devolverá la cadena getCustomers; de modo que nuestro servicio web pueda procesarla a partir de dicho punto.

Recibir y Enviar datos en formato JSON

Para mantener este tutorial lo más breve posible, nuestra API sólo responderá a dos métodos: GetAll y AddAlbum. Con el primero la aplicación cliente obtendrá en formato JSON el nombre de todos los álbumes almacenados en la base de datos. Con el segundo método, la aplicación cliente pedirá al servicio Web que añada un nuevo registro (un nuevo álbum) en la tabla correspondiente de nuestra base de datos de ejemplo.

¿Cómo podemos procesar los datos proporcionados en la petición desde el evento HandleURL? Aquí es donde encontraremos de utilidad otra de las propiedades del objecto Request. La propiedad Body contiene los datos enviados como parte de la solicitud y que no están presentes en las cabeceras. En general, contendrá los datos adicionales proporcionados mediante los verbos PUT y POST.

Ahora podemos incluir el siguiente código en el manejador HandleURL:

Select Case Request.Path // Cuál es el método recibido como parte de la petición? (URL)
Case "GetAll"
  Var output As JSONItem = GetAllAlbums // Asignamos los datos procesados a la variable Output, en el caso de que el método recibido sea 'GetAllAlbums'
  
  Response.Header("charset") = "utf-8"
  Response.MIMEType = "application/json"
  Response.Status = 200
  response.write( output.ToString ) // y lo enviamos de vuelta como respuesta al cliente que ha realizado la petición, convirtiendo el JSON a su representación como cadena.
  
Case "AddAlbum"
  Var data As String = Request.Body.DefineEncoding(encodings.UTF8) // Aplicamos la codificación de texto deseada sobre los datos recibidos
  Var Input As JSONItem = New JSONItem( data ) // Creamos un nuevo objeto JSONItem a partir de dichos datos
  addNewAlbum( Input.Value("newAlbum") ) // En este caso la petición consiste en añadir un nuevo álbum a la base de datos, pasando por tanto los datos recibidos como parte de la entrada
End Select

Return True

Como puedes ver utilizamos la estructura Select…Case para decidir cuál es el código que se ha de ejecutar como respuesta a cualquiera de los dos métodos soportados por nuestra API, lo cual está basado en el componente almacenado en la propiedad Path tal y como indicamos anteriormente. De modo que si la petición utiliza el método GetAll de la API en el URL, entonces nos encargaremos de invocar el método GetAllAlbums en nuestra aplicación Xojo Web. Tras procesar la información la devolveremos como un nuevo JSONItem como respuesta al cliente.

En el caso de que el método del URL sea AddAlbum entonces nos encargaremos de guardar los datos recibidos en la variable data, y definiremos la codificación del texto a UTF-8 para evitar cualquier posible problema en cualquier procesamiento realizado posteriormente. Por supuesto, nuestro ejemplo siempre espera recibir los datos adicionales en formato JSON, de modo que crearemos una nueva instancia JSONItem a partir de dichos datos.

¿Cómo enviamos la respuesta a una petición recibida? Realmente simple: utilizando el método Write sobre el objeto Response, y pasando como parámetro el texto que queramos enviar de vuelta. En nuestro ejemplo, este es el JSONItem referenciado por la variable Output. También utilizamos el objeto Response para definir la codificación del texto a UTF-8 y el tipo MIME a “Application/json”; además de ajustar el valor de estado a 200 (es decir, la petición se ha gestionado correctamente).

Si recibimos una petición que utiliza el método AddAlbum de nuestra API, entonces nos encargamos de llamar al método addNewAlbum pasando como parámetro el objeto JSONItem encargado de contener los datos recibidos como parte de la petición (es decir, el objeto referenciado por la variable Input). De hecho, la estructura del nuevo registro se almacena dentro del nodo raíz newAlbum del objeto JSONItem.

Comunicarse con la Base de Datos

Si bien el evento HandleURL está a cargo de procesar la petición recibida, utilizaremos un par de métodos en nuestra aplicación de ejemplo que serán los encargados de servir como enlace entre la API y la base de datos en el backend, tanto para recuperar los álbumes como para añadir un nuevo registro de álbum. (En una aplicación de “el mundo real” querrás validar y realizar las comprobaciones necesarias sobre los datos antes de acceder a la base de datos).

Selecciona el objeto App de nuevo y utiliza la opción Insert > Method para añadir el método GetAllAlbums utilizando los siguientes valores en el Panel Inspector:

    • Method Name: getAllAlbums
    • Return Type: JSONItem
    • Scope: Private

Este es el código encargado de generar el JSONItem que Escribiremos  como parte de la respuesta a la petición, incluyendo el nodo para cada uno de los registros de la base de datos en la tabla Album de nuestra base de ejemplo:

Var rc As RowSet = database.SelectSQL("Select * from album order by title asc") // Obtenemos el conjunto de registros como resultado de las selección: todos los registros
Var item As New JSONItem

If rc.RowCount > 0 Then // Tenemos registros en el RowSet
  
  While Not rc.AfterLastRow // de modo que los iteramos
    Var d As New Dictionary // creando un nuevo diccionario para cada registro, y que convertiremos en un nodo
    d.Value("artistid") = rc.Column("artistid").StringValue // asignado el ID de registro al nombre 'ArtistId' del JSONItem
    d.Value("title") = rc.Column("title").StringValue // el valor Title al campo 'Title' del JSONItem
    item.Value(rc.Column("albumid").StringValue) = d // Ya sabes que puedes asignar un Diccionario como valor de un nodo JSONItem. ¡Muy útil!
    
    rc.MoveToNextRow
  Wend
  
  rc.Close
  
End If

var output As New JSONItem

output.Value("AllAlbums") = item // Luego añadimos todos estos registros en un nodo padre

Return output // y devolvemos el resultado

A continuación, crea un nuevo método con el nombre addNewAlbum. Este será el que utilice nuestro servicio web para añadir un nuevo registro a la base de datos, usando para ello los datos recibidos como parte de la petición. Utiliza los siguientes valores en el Panel Inspector asociado:

      • Method Name: addNewAlbum
      • Paramters: item as JSONItem
      • Scope: Private

Y teclea el siguiente código en el Editor de Código asociado con el método:

Var title As String = item.Value("Title") // obtenemos los datos asociados con el campo "Title"
Var artistid As String = item.Value("ArtistId") // y el campo 'ArtistID'

database.ExecuteSQL("insert into album(title,artistid) values(?, ?)", title, artistid ) // e insertamos dichos datos como nuevo registro en la tabla de la base de datos

Como puedes ver el código es bastante simple: obtiene los valores para las claves recibidas en los nodos del objeto JSONitem y los utiliza como parte de la sentencia SQL encargada de añadir un nuevo registro a la base de datos.

Un Servicio Web… ¡listo para servir!

Como has visto, el código y la estructura de la aplicación web son realmente mínimos. Por supuesto este es un ejemplo simple, pero te proporciona una buena idea sobre el tipo de posibilidades que ofrecen los servicios web y cuan rápido puedes implementarlos utilizando el lenguaje Xojo que ya conoces. Por supuesto, puedes ejecutar la aplicación web desde el IDE (asegúrate, eso sí, de seleccionar el puerto 8081 como Debug Port).

2 comentarios en “Servicios Web (I): Xojo Web a tu servicio (API 2.0)

  1. Fernando Pinto

    Buenas Noches, Javier una consulta he tratado de aplicar el codigo que expones aqui, lo adapto a usar un servidor de mysql,pero se bloquea el programa en la linea que marque en el codigo,expuse mi problema en el foro pero nadie me ha respondido, que opinas, te paso el codigo, por si me puedes aporta una idea del problema:

    Dim db2 As mySQLCommunityServer
    db2=New mySQLCommunityServer
    db2.host=”192.168.1.20″
    db2.Port=9780
    db2.databaseName=”issadmin”
    db2.userName=”prueba”
    db2.Password=”prueba2211**”
    Var sql As String
    sql = “SELECT now() as dueno;”
    Var jsonResults As New JSONItem

    db2.Connect
    Var rc As RowSet = db2.SelectSQL(“select dueno,cedula,CodControl from agencias;”) // Obtenemos el conjunto de registros como resultado de las selección: todos los registros
    Var item As New JSONItem

    If rc.RowCount > 0 Then // Tenemos registros en el RowSet

    While Not rc.AfterLastRow // de modo que los iteramos
    Var d As New Dictionary // creando un nuevo diccionario para cada registro, y que convertiremos en un nodo
    d.Value(“artistid”) = rc.Column(“dueno”).StringValue // asignado el ID de registro al nombre ‘ArtistId’ del JSONItem
    d.Value(“title”) = rc.Column(“cedula”).StringValue // el valor Title al campo ‘Title’ del JSONItem

    //////Linea que me genera el error……./////////
    item.Value(rc.Column(“CodControl”).StringValue) = d // Ya sabes que puedes asignar un Diccionario como valor de un nodo JSONItem. ¡Muy útil!

    rc.MoveToNextRow
    Wend

    rc.Close

    End If

    var output As New JSONItem

    output.Value(“AllAlbums”) = item // Luego añadimos todos estos registros en un nodo padre

    Return output // y devolvemos el resultado

    1. Javier Rodriguez

      Hola!

      Sería útil saber cuál es el error que recibes y sobre qué versión de Xojo estás ejecutando la app, así como el sistema operativo (Windows, macOS, Linux)?

      También sería interesante que pusieras un punto de parada en la línea:

      item.Value(rc.Column("CodControl").StringValue) = d

      …y ver en el panel del depurador qué valor tiene en ese momento la columna “CodControl”, así como los contenidos del diccionario “d” en ese punto.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *