WCF (II) : Creación de un servicio web simple


Comprendidos los principios sobre los que se basa Windows Communication Foundation, es momento de crear un pequeño servicio básico que maneje una lista interna de clientes. No haremos uso de bases de datos, puesto que el objetivo principal de este artículo es centrarnos en la comunicación. El último artículo dedicado a Entity Framework desarrolla, precisamente, un ejemplo más específico del uso de Entity Framework para desarrollar un servicio web RESTful mediante WCF.

Como vimos en el artículo anterior, a grandes rasgos un servicio WCF se compondrá de cuatro elementos:

  • Contrato de datos: estructura de datos que será intercambiada por el servicio.
  • Contrato de servicio: interfaz que expondrá los métodos que pueden ser invocados por el cliente.
  • Implementación del servicio: codificación del contrato de servicio, incorporará la lógica interna del servicio.
  • Configuración: fichero XML con información sobre dirección, nombre o protocolo.

Comenzaremos creando el servicio para pasar, a continuación, por el primero de estos elementos: el contrato de datos.

Creación del servicio

Para comenzar la codificación de nuestro servicio, crearemos un nuevo proyecto WCF, específicamente del tipo WCF Service Library, que generará una biblioteca DLL que contendrá la funcionalidad del servicio.

Visual Studio creará ahora un esqueleto de nuestro primer servicio web, que tendrá un aspecto similar al siguiente:

Estos ficheros tendrán los nombres IService1.cs y Service1.cs. Comenzaremos eliminando estos archivos y creando el primero de los elementos que conformará el servicio.

Contrato de datos (DataContract)

Añadiremos una nueva clase que simbolizará la estructura de datos que nuestro servicio web intercambiará con un potencial cliente: el DataContract o contrato de datos. Llamaremos a la clase Cliente.cs.

Esta información no será más que un POCO (Plain Old CLR Object) que contendrá los campos que queremos enviar a través de la red. Un POCO no es más que una estructura simple, sin funcionalidad, que contiene una serie de campos que normalmente simbolizará un registro en una base de datos relacional desde una perspectiva orientada a objetos. A nivel estructural equivaldría a los struct de C.

Por tanto, haremos la clase pública y añadiremos los campos que queremos exponer, que también tendrán visibilidad pública.


namespace WcfServices
{
    public class Cliente
    {
        public int IdCliente;
        public string Nombre;
        public DateTime FechaNacimiento;
    }
}

Para que nuestro POCO adquiera la categoría de contrato de datos, será necesario adornar la clase y sus elementos con un par de atributos heredados de System.Runtime.Serialization. Por lo tanto, añadimos el using correspondiente y asignamos el atributo DataContract a la clase y el atributo DataMember a cada uno de los elementos de la clase.


namespace WcfServices
{
    [DataContract]          // System.Runtime.Serialization
    public class Cliente
    {
        [DataMember]
        public int IdCliente;

        [DataMember]
        public string Nombre;

        [DataMember]
        public DateTime FechaNacimiento;
    }
}

Contrato de Servicio (ServiceContract)

El segundo elemento que deberemos configurar será el contrato de servicio, interfaz que contendrá la firma de los métodos que el servicio expondrá de forma pública. Añadiremos, por lo tanto, una nueva interfaz a la que llamaremos IClienteService.

Esto hará que se cree una interfaz vacía con el siguiente aspecto:


namespace WcfServices
{
    interface IClienteService
    {
    }
}

Nuevamente, haremos la interfaz pública, y añadiremos la firma de los métodos que queremos que esta incluya. Para este caso concreto, crearemos un método de recuperación, otro de inserción, otro de modificación y un último método de eliminación. Recordemos que en una interfaz no debemos indicar la visibilidad de los métodos que ésta incluye.


namespace WcfServices
{
    public interface IClienteService
    {
        List<Cliente> ObtenerClientes();
        void InsertarCliente(Cliente c);
        void ModificarCliente(Cliente c);
        void EliminarCliente(int id);
    }
}

Por último, importaremos el espacio de nombres System.ServiceModel y adornaremos la interfaz con el atributo ServiceContract. Los métodos de la interfaz, por su parte, irán adornados con el atributo OperationContract, ya que establecerán el contrato de las operaciones que el servicio puede realizar.


namespace WcfServices
{
    [ServiceContract]                   // System.ServiceModel
    public interface IClienteService
    {
        [OperationContract]
        List<Cliente> ObtenerClientes();

        [OperationContract]
        void InsertarCliente(Cliente c);

        [OperationContract]
        void ModificarCliente(Cliente c);

        [OperationContract]
        void EliminarCliente(int id);
    }
}

Implementación del contrato

Contando con datos y operaciones, es el momento de codificar una clase que implemente la interfaz que define el contrato de servicio con sus correspondientes operaciones. Por lo tanto, crearemos una nueva clase, a la que denominaremos ClienteService, que implemente la interfaz IClienteService.


namespace WcfServices
{
    class ClienteService : IClienteService
    {
    }
}

Para que Visual Studio genere el cuerpo de las funciones que debemos implementar, haremos click derecho sobre el nombre de la interfaz y seleccionaremos la opción Implement Interface.

Esto hará que se genere el código correspondiente a la firma de los métodos de la interfaz, indicándonos qué es lo que debemos codificar.


    class ClienteService : IClienteService
    {
        public List<Cliente> ObtenerClientes()
        {
            throw new NotImplementedException();
        }

        public void InsertarCliente(Cliente c)
        {
            throw new NotImplementedException();
        }

        public void ModificarCliente(Cliente c)
        {
            throw new NotImplementedException();
        }

        public void EliminarCliente(int id)
        {
            throw new NotImplementedException();
        }
    }

Nuevamente, importaremos el espacio de nombres System.ServiceModel y asignaremos el atributo ServiceBehavior a la clase. Además, crearemos un atributo estático de carácter privado que simbolizará nuestra fuente de datos. Consistirá en una lista de clientes sobre las que realizaremos las operaciones del servicio.


    [ServiceBehavior]
    public class ClienteService : IClienteService
    {
        private static List<Cliente> listaClientes = new List<Cliente>();

El atributo ServiceBehavior puede aceptar parámetros que permite configurar aspectos específicos del servicio, como el tipo de concurrencia o si el servicio funcionará en modo singleton. Pueden consultarse en la MSDN aquí.

Finalmente, añadimos la funcionalidad deseada a cada método. Por ejemplo, codificaríamos algo como lo siguiente:


    [ServiceBehavior]
    public class ClienteService : IClienteService
    {
        private static List<Cliente> listaClientes = new List<Cliente>();

        public List<Cliente> ObtenerClientes()
        {
            return listaClientes;
        }

        public void InsertarCliente(Cliente c)
        {
            // Calculamos el ID del siguiente elemento
            if (listaClientes.Count == 0)
                c.IdCliente = 1;
            else
                c.IdCliente = listaClientes.Max(cliente => cliente.IdCliente) + 1;

            // Añadimos el cliente a la lista
            listaClientes.Add(c);
        }

        public void ModificarCliente(Cliente c)
        {
            // Recuperamos el cliente cuyo ID coincide con el pasado como parámetro
            Cliente clienteModif = listaClientes.Where(cliente => cliente.IdCliente == c.IdCliente).First();

            // Si el registro existe, se modifica
            if (clienteModif != null)
            {
                clienteModif.Nombre = c.Nombre;
                clienteModif.FechaNacimiento = c.FechaNacimiento;
            }

        }

        public void EliminarCliente(int id)
        {
            // Recuperamos el cliente cuyo ID coincide con el pasado como parámetro
            Cliente clienteEliminar = listaClientes.Where(cliente => cliente.IdCliente == id).First();

            // Si el registro existe, se elimina
            if (clienteEliminar != null)
            {
                listaClientes.Remove(clienteEliminar);
            }
        }
    }

Con esto concluiríamos la codificación del servicio. Sólo nos quedan dos pasos: configurar los endpoints y realizar una prueba para comprobar su funcionamiento. En realidad faltaría un tercer paso, su despliegue, pero esto lo dejaremos para una ocasión posterior.

Configuración

Para accede a la configuración del servicio, podemos editar directamente el fichero de configuración App.config / web.config o bien podemos dejar que el asistente nos facilite esta tarea. Para acceder a él, haremos click derecho sobre el App.config y seleccionaremos la opción Edit WCF Configuration.

Esto abrirá un diálogo que nos mostrará la configuración de nuestro servicio, mostrando dos endpoints. Sin embargo, la configuración que se nos muestra es la configuración inicial generada al crear el proyecto, por lo que no nos será de mucha utilidad.

Para solucionarlo, comenzaremos por compilar el servicio web de modo que se genere el fichero dll objetivo.

Hecho esto, seleccionaremos el servicio preconfigurado y pulsaremos el botón […] que se muestra a la derecha del nombre del servicio cuando éste es seleccionado.

A continuación buscaremos la dll que acabamos de generar. La encontraremos en el directorio /bin/Debug de nuestro proyecto.

Esto hará que el nombre del servicio cambie, mostrando la configuración correspondiente al servicio que hemos codificado.

Desplegaremos la carpeta Endpoints y seleccionaremos el primero de ellos, realizando el mismo proceso de seleccionar el fichero dll para localizar el contrato del servicio.

Si desplegamos el combo Binding podremos observar todas las posibilidades que WCF nos oferta como métodos de enlace. Sin embargo, también dejaremos esto para posteriores artículos, seleccionando el valor por defecto, basicHttpBinding.

Prueba

Si pulsamos F5 una vez guardada la configuración, se lanzará el cliente de prueba del servicio. Este cliente es generado automáticamente por Visual Studio, y nos permite realizar invocaciones a los métodos expuestos por el servicio a través de una interfaz de usuario.

Haremos doble click sobre el método InsertarCliente e introduciremos los datos solicitados. Hecho esto, pulsaremos el botón Invoke, que hará que se ejecute el método del servicio y se obtenga la respuesta correspondiente, en caso de que ésta exista. Repetiremos el proceso un par de veces más para que la lista mantenida en el servidor tenga datos con los que realizar pruebas.

Si hacemos doble click sobre ObtenerClientes(), la respuesta no se mostrará vacía, sino que estará compuesta por un listado de objetos Cliente. Recordemos que Cliente es en este caso el contrato de datos que es intercambiado mediante los métodos expuestos por el contrato de servicio.

La modificación también solicitará un objeto de la clase cliente, que cambiará los datos del Cliente cuyo ID coincida con el incluido dentro del objeto.

Si realizamos una nueva consulta, veremos que, efectivamente, los datos se han actualizado.

Finalmente, la eliminación solicitará un entero con el ID del elemento a eliminar. Procedemos, por lo tanto, a proporcionárselo y a invocar el servicio.

Si volvemos a consultar, comprobamos que, efectivamente, el registro habrá sido eliminado de la lista.

Para finalizar con la introducción, el cliente de prueba de WCF nos permite comprobar también el contenido de los mensajes. En este caso, se trata de mensajes SOAP. Como podremos recordar del artículo anterior, el mensaje SOAP se componía de un envoltorio (envelope) que contiene una cabecera (header) y un cuerpo (body). El resultado de la petición y de la respuesta será el siguiente:

2 comentarios

  1. El tutorial esta bastante bien. Estoy empezando a trabajar con WCF y WPF y me ha ayudado mucho a entender bastantes cosas pero hay una que no me queda muy claro.

    Basandome en tu ejemplo, en el archivo app.config hay un endpint para poder acceder al servicio desde cualquier cliente. En este caso es http://locahost:8733/

    Si el puerto 8733 esta ocupado, ¿El servicio se levantara correctamente o dara algún problema? En caso de dar problema, ¿como podrías definir ese puerto de forma dinamica en el servicio?

  2. Amigo vengo del futuro y tu explicación fue muy buena, muchas gracias, me sirvió mucho para entender los conceptos básicos.

Deja un comentario