Acceso a Servicios Web REST en Android
(PARTE 1)
Rest se asienta sobre el protocolo HTTP como mecanismo de transporte entre el cliente y el servidor y en cuanto a los formatos de los datos transmitidos a diferencia de SOAP no se impone ninguno en concreto aunque lo mas habitual actualmente es intercambiar la información en formato XML o JSON, para el caso de SOAP se usa XML, pero en este caso utilizaremos JSON para construir nuestro modelo.
Antes de continuar indicaremos por que JSON ??
JSON es un formato bastante ligero empleado netamente para el intercambio de datos, este es un subconjunto de notaciones para objetos empleados en JavaScript, como este formato es mas sencillo resulta esta la mejor alternativa frente al XML, otra ventaja es al momento de procesarse es mucho mas rápido el JSON en cualquier navegador siendo esta la mayor ventaja frente a cualquier desarrollo de algún proyecto.
5 razones por las cuales JSON vence a XML en proyectos Web
- JSON soporta dos tipos de estructuras, una de ellas son objetos que contienen una colección de pares llave-valor y el otro tipo se trata de arrays de valores. Esto proporciona una gran sencillez en las estructuras.
- JSON no tiene espacios de nombres, cada objeto es un conjunto de claves independientes de cualquier otro objeto.
- JSON no necesita ser extensible por que es flexible por sí solo. Puede representar cualquier estructura de datos pudiendo añadir nuevos campos con total facilidad.
- JSON es mucho mas simple que XML, el cual proporciona pesadas tecnologías que le avalan (Scheme, XSLT, XPath).
- JSON es optimista y no requiere de este tipo de tecnologías, confía en el desarrollador.
Continuando con nuestro caso práctico, el cual será desarrollado bajo estas herramientas :
Programa: Visual Studio
Lenguaje: C#
Framework : ASP.NET MVC 3
Empezemos :
En la ventana de opciones del proyecto dejaremos todos los datos que aparecen por defecto y seleccionaremos como plantilla “Empty” para crear una aplicación vacía.
Esto debería crearnos el nuevo proyecto con la estructura de carpetas necesaria, que como veréis es bastante elaborada. En nuestro caso vamos a crear el servicio web de forma aislada del resto de la aplicación web, y para ello lo primero que vamos a hacer es añadir una nueva Area al proyecto, a la que llamaremos por ejemplo “Api“, lo que nos creará una estructura de carpetas similar a la de la aplicación principal pero dentro de una carpeta independiente. Esto nos permite aislar todo el código y recursos de nuestro servicio web del resto de la aplicación web (que en nuestro caso no existirá porque no es el objetivo de este artículo, pero que podríamos crear sin problemas si lo necesitáramos).
Con esto, la estructura de nuestro proyecto será la siguiente:
Una vez que ya tenemos preparada toda la estructura de nuestro proyecto empecemos a añadir los elementos necesarios. Lo primero que vamos a crear será una nueva clase Cliente, igual que hicimos en el ejemplo anterior con SOAP. La colocaremos en la carpeta “Api/Models” y el código es el mismo que ya vimos:
namespace
ServicioWebRest.Areas.Api.Models
{
public
class
Cliente
{
public
int
Id {
get
;
set
; }
public
string
Nombre {
get
;
set
; }
public
int
Telefono {
get
;
set
; }
}
}
El siguiente elemento a añadir será una nueva clase que contenga todas las operaciones que queramos realizar sobre nuestra base de datos de clientes. Llamaremos a la clase ClienteManager. En este caso sí vamos a añadir las cuatro operaciones básicas sobre clientes, y una adicional para obtener el listado completo, de forma que más tarde podamos mostrar la implementación en Android de todos los posibles tipos de llamada al servicio. Los métodos que añadiremos serán los siguientes:
Los dos primeros métodos nos servirán para recuperar clientes de la base de datos, tanto por su ID para obtener un cliente concreto, como el listado completo que devolverá una lista de clientes. Los otros tres métodos permitirán insertar, actualizar y eliminar clientes a partir de su ID y los datos de entrada (si aplica). El código de todos estos métodos es análogo a los ya implementados en el caso de SOAP, por lo que no nos vamos a parar en volverlos a comentar, tan sólo decir que utilizan la api clásica de ADO.NET para el acceso a SQL Server. En cualquier caso, al final del artículo tenéis como siempre el código fuente completo para poder consultar lo que necesitéis. A modo de ejemplo veamos la implementación de los métodos
ObtenerClientes() e InsertarCliente().
public
bool
InsertarCliente(Cliente cli)
{
SqlConnection con =
new
SqlConnection(cadenaConexion);
con.Open();
string
sql =
"INSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre, @tel efono)"
;
SqlCommand cmd =
new
SqlCommand(sql,con);
cmd.Parameters.Add(
"@nombre"
, System.Data.SqlDbType.NVarChar).Value = cli.N ombre;
cmd.Parameters.Add(
"@telefono"
, System.Data.SqlDbType.Int).Value = cli.Tele fono;
int
res = cmd.ExecuteNonQuery();
con.Close();
return
(res == 1);
}
public
List<Cliente> ObtenerClientes()
{
List<Cliente> lista =
new
List<Cliente>();
SqlConnection con =
new
SqlConnection(cadenaConexion);
con.Open();
string
sql =
"SELECT IdCliente, Nombre, Telefono FROM Clientes"
;
SqlCommand cmd =
new
SqlCommand(sql,con);
SqlDataReader reader =
cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
while
(reader.Read())
{
Cliente cli =
new
Cliente();
cli =
new
Cliente();
cli.Id = reader.GetInt32(0);
cli.Nombre = reader.GetString(1);
cli.Telefono = reader.GetInt32(2);
lista.Add(cli);
}
reader.Close();
return
lista;
}
Hasta ahora, todo el código que hemos escrito es bastante genérico y nada tiene que ver con que nuestro proyecto sea de tipo MVC. Sin embargo, los dos siguientes elementos sí que están directamente relacionados con el tipo de proyecto que tenemos entre manos
La acción Clientes es muy sencilla, se limitará a llamar al método ObtenerClientes() y formatear los resultados como JSON. Para hacer esto último basta con crear directamente un objeto JsonResultllamado al método Json() pasándole como parámetro de entrada el objeto a formatear. Todo esto se reduce a una sola linea de código:
1
2
3
4
5
6
[HttpGet]
public
JsonResult Clientes()
{
return
Json(
this
.clientesManager.ObtenerClientes(),
JsonRequestBehavior.AllowGet);
}
Habréis notado también que hemos precedido el método con el atributo [HttpGet]. Para intentar explicar esto me hace falta seguir hablando de los principios de diseño de REST. Este tipo de servicios utiliza los propios tipos de petición definidos por el protocolo HTTP para diferenciar entre las operaciones a realizar por el servicio web. Así, el propio tipo de petición HTTP realizada (GET, POST, PUT o DELETE), junto con la dirección URL especificada en la llamada, nos determinará la operación a ejecutar por el servicio web. En el caso ya visto, el atributo [HttpGet] nos indica que dicho método se podrá ejecutar al recibirse una petición de tipo GET.
Entenderemos todo esto mejor ahora cuando veamos el código de la acción Cliente(). En esta acción, dependiente del tipo de petición HTTP recibida, tendremos que llamar a un método u otro del servicio web. Así, usaremos POST para las inserciones de clientes, PUT para las actualizaciones, GET para la consulta por ID y DELETE para las eliminaciones. En este caso no precedemos el método por ningún atributo, ya que la misma acción se encargará de tratar diferentes tipos de petición.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public
JsonResult Cliente(
int
? id, Cliente item)
{
switch
(Request.HttpMethod)
{
case
"POST"
:
return
Json(clientesManager.InsertarCliente(item));
case
"PUT"
:
return
Json(clientesManager.ActualizarCliente(item));
case
"GET"
:
return
Json(clientesManager.ObtenerCliente(id.GetValueOrDefault()),
JsonRequestBehavior.AllowGet);
case
"DELETE"
:
return
Json(clientesManager.EliminarCliente(id.GetValueOrDefault()));
}
return
Json(
new
{ Error =
true
, Message =
"Operación HTTP desconocida"
});
}
Algunos de vosotros seguro que os estáis preguntando cómo distinguirá el servicio cuándo llamar a la acción Clientes() para obtener el listado completo, o a la acción Cliente() para obtener un único cliente por su ID, ya que para ambas operaciones hemos indicado que se recibirá el tipo de petición http GET.
Pues bien, aquí es donde nos va a ayudar el último elemento a añadir al servicio web. Realmente no lo añadiremos, sino que lo modificaremos, ya que es un fichero que ya ha creado Visual Studio por nosotros. Se trata de la clase ApiAreaRegistration. La función de esta clase será la de dirigir las peticiones recibidas hacia una acción u otra del controlador según la URL utilizada al realizarse la llamada al servicio web.
En nuestro caso de ejemplo, vamos a reconocer dos tipos de URL. Una de ellas para acceder a la lista completa de cliente, y otra para realizar cualquier acción sobre un cliente en concreto:
Cada uno de estos patrones tendremos que registrarlos mediante el método MapRoute() dentro del método RegisterArea() que ya tendremos creado dentro de la clase ApiAreaRegistration. Así, para registrar el primer tipo de URL haremos lo siguiente:
1
2
3
4
5
6
7
8
9
context.MapRoute(
"AccesoClientes"
,
"Api/Clientes"
,
new
{
controller =
"Clientes"
,
action =
"Clientes"
}
);
Como primer parámetro de MapRoute() indicamos un nombre descriptivo para el patrón de URL. El segundo parámetro es el patrón en sí, que en este caso no tiene partes variables. Por último indicamos el controlador al que se dirigirán las peticiones que sigan este patrón eliminando el sufijo “Controller” (en nuestro caso será el controlador ClientesController) y la acción concreta a ejecutar dentro de dicho controlador (en nuestro caso la acción Clientes()).
Para el segundo tipo de URL será muy similar, con la única diferencia de que ahora habrá una parte final variable que se corresponderá con el ID del cliente y que asignaremos al parámetro “id” de la acción. En este caso además, dirigiremos la petición hacia la acción Cliente(), en vez de Clientes().
1
2
3
4
5
6
7
8
9
10
context.MapRoute(
"AccesoCliente"
,
"Api/Clientes/Cliente/{id}"
,
new
{
controller =
"Clientes"
,
action =
"Cliente"
,
id = UrlParameter.Optional
}
);
Como todo esto en cuenta, y por recapitular un poco, las posibles llamadas a nuestro servicio serán las siguientes:
GET /Api/Clientes
Recuperará el listado completo de clientes y lo devolverá en formato JSON.
GET /Api/Clientes/Cliente/3
Recuperará el cliente con el ID indicado en la URL y lo devolverá en formato JSON.
POST /Api/Clientes/Cliente { Nombre:”nombre”, Telefono:1234 }
Insertará un nuevo cliente con los datos aportados en la petición en formato JSON.
PUT /Api/Clientes/Cliente/3 { Id:3, Nombre:”nombre”, Telefono:1234 }
Actualizará el cliente con el ID indicado en la URL con los datos aportados en la petición en formato JSON.
DELETE /Api/Clientes/Cliente/3
Eliminará el cliente con el ID indicado en la URL.
Llegados aquí, tan sólo tenemos que ejecutar nuestro proyecto y esperar a que se abra el navegador web. En principio no se mostrará un error por no encontrar la página principal de la aplicación, ya que no hemos creado ninguna, pero nos asegurará que el servidor de prueba está funcionando, por lo que nuestro servicio debería responder a peticiones.
Así, si escribimos en la barra de direcciones por ejemplo la siguiente dirección (el puerto puede variar):
http://localhost:1234/Api/Clientes/Cliente/4
deberíamos recibir un fichero en formato JSON que contuviera los datos del cliente con ID = 4 de nuestra base de datos. Sería un fichero con contenido similar al siguiente:
{"Id":4,"Nombre":"cliente4","Telefono":4444
vamos a crear una aplicación de ejemplo que llame a las distintas funciones de nuestro servicio web. En este caso la aplicación se compondrá de 5 botones, uno por cada una de las acciones que hemos implementado en el servicio web (insertar, actualizar, eliminar, recuperar un cliente, y listar todos los clientes).
Android incluye todo lo necesario para realizar la conexión y llamada a los métodos del servicio, y tratamiento de resultados en formato JSON.
Como ya hemos comentado, al trabajar con servicios web de tipo REST, las llamadas al servicio no se harán a través de una única URL, sino que se determinará la acción a realizar según la URL accedida y la acción HTTP utilizada para realizar la petición (GET, POST, PUT o DELETE). En los siguientes apartados veremos uno a uno la implementación de estos botones.
Insertar un nuevo cliente
Como ya comentamos en el artículo anterior, la inserción de un nuevo cliente la realizaremos a través de la siguiente URL:
http://10.0.2.2:2731/Api/Clientes/Cliente
Utilizaremos la acción http POST y tendremos que incluir en la petición un objeto en formato JSON que contenga los datos del nuevo cliente (tan sólo Nombre y Teléfono, ya que el ID se calculará automáticamente). El formato de este objeto de entrada será análogo al siguiente:
{Nombre:”cccc”, Telefono:12345678}
Pues bien, para conseguir esto comenzaremos por crear un nuevo objeto HttpClient, que será el encargado de realizar la comunicación HTTP con el servidor a partir de los datos que nosotros le proporcionemos. Tras esto crearemos la petición POST creando un nuevo objeto HttpPost e indicando la URL de llamada al servicio. Modificaremos mediante setHeader() el atributo http content-type para indicar que el formato de los datos que utilizaremos en la comunicación, que como ya indicamos será JSON (cuyo MIME-Type correspondiente es “application/json“).
HttpClient httpClient =
new
DefaultHttpClient();
HttpPost post =
post.setHeader(
"content-type"
,
"application/json"
);
El siguiente paso será crear el objeto JSON a incluir con la petición, que deberá contener los datos del nuevo cliente a insertar. Para ello creamos un nuevo objeto JSONObject y le añadimos mediante el métodoput() los dos atributos necesarios (nombre y teléfono) con sus valores correspondientes, que los obtenemos de los cuadros de texto de la interfaz, llamados txtNombre y txtTelefono.
Por último asociaremos este objeto JSON a nuestra petición HTTP convirtiéndolo primero al tipoStringEntity e incluyéndolo finalmente en la petición mediante el método setEntity().
//Construimos el objeto cliente en formato JSON
JSONObject dato =
new
JSONObject();
dato.put(
"Nombre"
, txtNombre.getText().toString());
dato.put(
"Telefono"
, Integer.parseInt(txtTelefono.getText().toString()));
StringEntity entity =
new
StringEntity(dato.toString());
post.setEntity(entity);
Una vez creada nuestra petición HTTP y asociado el dato de entrada, tan sólo nos queda realizar la llamada al servicio mediante el método execute() del objeto HttpClient y recuperar el resultado mediante getEntity(). Este resultado lo recibimos en forma de objeto HttpEntity, pero lo podemos convertir fácilmente en una cadena de texto mediante el método estático EntityUtils.toString().
HttpResponse resp = httpClient.execute(post);
String respStr = EntityUtils.toString(resp.getEntity());
if
(respStr.equals(
"true"
))
lblResultado.setText(
"Insertado OK."
);
En nuestro caso, el método de inserción devuelve únicamente un valor booleano indicando si el registro se ha insertado correctamente en la base de datos, por lo que tan sólo tendremos que verificar el valor de este booleano (“true” o “false”) para conocer el resultado de la operación, que mostraremos en la interfaz en una etiqueta de texto llamada lblResultado.
Como ya dijimos en el apartado anterior sobre servicios SOAP, a partir de la versión 3 de Android no se permite realizar operaciones de larga duración dentro del hilo principal de la aplicación, entre ellas conexiones a internet como estamos haciendo en esta ocasión. Para solucionar este problema y que la aplicación funcione correctamente en todas las versiones de Android debemos hacer la llamada al servicio mediante una tarea asíncrona, o AsynTask, que se ejecute en segundo plano. Una vez más no entraré en detalles porque el curso contiene un capítulo dedicado exclusivamente a este tema. A modo de ejemplo, el código anterior trasladado a una AsyncTask quedaría de la siguiente forma:
private
class
TareaWSInsertar
extends
AsyncTask<String,Integer,Boolean> {
protected
Boolean doInBackground(String... params) {
boolean
resul =
true
;
HttpClient httpClient =
new
DefaultHttpClient();
HttpPost post =
new
HttpPost(
"http://10.0.2.2:2731/Api/Clientes/Cliente"
);
post.setHeader(
"content-type"
,
"application/json"
);
try
{
//Construimos el objeto cliente en formato JSON
JSONObject dato =
new
JSONObject();
dato.put(
"Nombre"
, params[
0
]);
dato.put(
"Telefono"
, Integer.parseInt(params[
1
]));
StringEntity entity =
new
StringEntity(dato.toString());
post.setEntity(entity);
HttpResponse resp = httpClient.execute(post);
String respStr = EntityUtils.toString(resp.getEntity());
if
(!respStr.equals(
"true"
))
resul =
false
;
}
catch
(Exception ex)
{
Log.e(
"ServicioRest"
,
"Error!"
, ex);
resul =
false
;
}
return
resul;
}
protected
void
onPostExecute(Boolean result) {
if
(result)
{
lblResultado.setText(
"Insertado OK."
);
}
}
}
Y la llamada a la tarea asíncrona desde el evento onClick del botón de Insertar sería tan sencilla como ésta:
btnInsertar.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
TareaWSInsertar tarea =
new
TareaWSInsertar();
tarea.execute(
txtNombre.getText().toString(),
txtTelefono.getText().toString());
}
});
No hay comentarios:
Publicar un comentario