9 sept 2012

WCF 4 Parte 3: Más operaciones, actualizando referencia

Continuamos con nuestras notas del libro “Windows Communication Foundation 4”, volvemos a recalcar que esas notas son de carácter personal y lo ideal seria que cualquiera que quiera aprender WCF, lea el libro en cuestión.

En nuestros primeros dos post creamos un servicio WCF y un cliente que lo consume. Este servicio es en realidad muy simple: un único método que devuelve una lista de string con los id de los productos de nuestra base de datos. En este post añadiremos unas cuantas operaciones más.

Estas operaciones serán las siguientes DetalleProducto (devolverá la descripción y el precio de un producto dado un id), StockProducto (dado un id de producto devolverá el stock conjunto de todas las bodegas que tengan ese producto), AnadirStock (añadirá la cantidad del producto a la bodega según se indiquen en los parámetros).

Nuestro estilo es un desarrollo “contract-first”, el contrato primero. Así, primero diseñamos las operaciones en la clase interfaz del contrato y una vez definidos procedemos a su implementación. Comencemos entonces con la clase IProductoServicio, El código nos quedaría más o menos así:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace TestWCF
{
    //Esto define la clase que devuelve el detalle de un producto
    [DataContract]
    public class ProductoDetalle
    {
        [DataMember]
        public string ProductoId;

        [DataMember]
        public string Descripcion;

        [DataMember]
        public decimal? Precio;
    }

    [ServiceContract]
    public interface IProductoServicio
    {
        // devuelve el id de todos los productos
        [OperationContract]
        List<string> ListaProductos();

        //Devuelve el detalle de un producto
        [OperationContract]
        ProductoDetalle DetalleProducto(string ProductoID);

        //Devuelve el stock completo de un producto
        [OperationContract]
        int StockProducto(string ProductoID);

        //Añade la cantidad del producto indicados a la bodega indicada
        [OperationContract]
        bool AnadirStock(string ProductoID, int Bodega, int cantidad);
    }
}

Analicemos el código, lo primero que notamos es la clase ProductoDetalle, esta una clase etiquetada como contrato de datos (DataContract) y cada una de sus variables como DataMember, es similar a definir a un tipo compuesto. Esta clase la usaremos para devolver un objeto con el detalle del producto en la operación correspondiente, como es publica quedará disponible en el cliente también, por medio del proxy.

Por si no se percatan, la variable Precio es de tipo decimal? El signo de pregunta significa que es de tipo nullable, o sea, que es un tipo decimal que puede contener el valor null (un decimal normal no puede contener el valor null), en este escenario es necesario por cuanto en la base de datos el valor Precio esta como “Allow Null” (hubiese sido mejor no permitir nulos y colocar un valor cero por defecto, digamos que fue un desliz de diseño).

Luego se añadieron las tres operaciones propuestas, notar que DetalleProducto devuelve un “tipo” ProductoDetalle, la clase que definimos primero como DataContract. StockProducto devuelve un entero con la cantidad total de inventario de un producto. Así mismo AnadirStock devuelve un booleano, esto es verdadero si se pudo añadir la cantidad, falso si se produjo algún error.

Ya con nuestro contrato definido procedemos a la implementación de cada una de las operaciones en el servicio propiamente dicho. El archivo ProductoServicio.cs nos queda más o menos así:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using ModeloProductos;

namespace TestWCF
{
    public class ProductoServicio : IProductoServicio
    {

        // Devuelve el id de todos los productos
        public List<string> ListaProductos()
        {
            // Se crea la lista para los id de Productos
            List<string> lstProductos = new List<string>();
            try
            {
                // Conectando a la base de datos usando Entity Framework
                using (TestDBEntities db= new TestDBEntities ())
                {
                    // LINQ para obtener la lista de id de los productos
                    var productos = from producto in db.Productos 
                                   select producto.ProductoID;
                    lstProductos = productos.ToList();
                }
            }
            catch
            {
                // De momento no se implementa el manejo de excepciones
            }
            // devuelve la lista de productos
            return lstProductos;
        }

        // Devuelve el detalle de un producto
        public ProductoDetalle DetalleProducto(string ProductoID)
        {
            // Creamos la instancia de la clase Producto definida en el contrato
            ProductoDetalle DatosProducto = null;
            try
            {
                // conectamos con la base de datos por medio del Entity Framework
                using (TestDBEntities db = new TestDBEntities ())
                {
                    // LINQ para encuentrar el primer registro cuyo id corresponda con el parametro
                    Producto productoEncontrado = db.Productos.First(
                    p => String.Compare(p.ProductoID , ProductoID) == 0);

                    DatosProducto  = new ProductoDetalle ()
                    {
                        ProductoId = productoEncontrado.ProductoID,
                        Descripcion = productoEncontrado.Descripcion,
                        Precio = productoEncontrado.Precio 
                    };
                }
            }
            catch
            {
                //  De momento no se implementa el manejo de excepciones
            }
            // Retorna el detalle de producto
            return DatosProducto;
        }

        // Devuelve el stock completo de un producto
        public int StockProducto(string ProductoID)
        {
            // Obtiene el nivel total de stock del producto incluyendo todas bodegas
            int stock = 0;
            try
            {
                // conectamos con la base de datos por medio del Entity Framework
                using (TestDBEntities db = new TestDBEntities())
                {
                    // LINQ para sumar todo el stock de todas los bodegas
                    stock = (from i in db.Inventarios
                                  join p in db.Productos
                                  on i.ProductoID equals p.ProductoID
                             where String.Compare(p.ProductoID, ProductoID) == 0
                                  select (int)i.Stock).Sum();
                }
            }
            catch
            {
                // De momento no se implementa el manejo de excepciones
            }
            // Devuelve el stock total del producto
            return stock;
        }

        // Añade la cantidad del producto indicados a la bodega indicada
        public bool AnadirStock(string ProductoID, int Bodega, int cantidad)
        {
            //Añade la cantidad indicada para el producto y en la bodega indicados
            try
            {
                // conectamos con la base de datos por medio del Entity Framework
                using (TestDBEntities db = new TestDBEntities())
                {
                    // LINQ para aseguramos que el id del producto exista
                    string producto =
                    (from p in db.Productos
                     where String.Compare(p.ProductoID, ProductoID) == 0
                     select p.ProductoID).First();

                    // LINQ para extraer de la tabla el registro del inventario 
                    // correspondiente al producto y la bodega                    
                    Inventario inventario = db.Inventarios.First(
                    i => String.Compare(i.ProductoID , ProductoID) == 0 &&
                    i.BodegaID == Bodega );
                    // realizamos el update sobre el registro encontrado
                    inventario.Stock += cantidad;
                    // salvamos los cambios en la base de datos
                    db.SaveChanges();
                }
            }
            catch
            {
                // retorna false si se produjo algun error
                return false;
            }
            // Retorna true si la operacion fue exitosa
            return true;
        }
    }
}

Examinemos las operaciones que implementamos para discernir mejor el uso de LINQ. En la operación DetalleProducto utilizamos la función First de Linq para traernos el primer registro que corresponda con la comparación del id del producto. Además como esta comparación es entre strings lo realizamos utilizando el String.Compare, esta es una práctica habitual en la consultas vía Linq, ya que es la forma segura de comparar cadenas de caracteres. Una vez obtenido el registro vaciamos sus propiedades en nuestra clase de contrato de datos para enviarla al cliente.

En la operación StockProducto usamos una sentencia Linq un poco más complicada: realizamos un join entre las tablas Producto e Inventario, enlazadas por medio de la columna ProductoId, además buscamos por el parámetro haciendo la comparación con el String.Compare tal y como señalamos anteriormente. Finalmente utilizamos la operación de agregación “sum” para sumar los stocks de todas las bodegas. El resultado se lo asignamos a la variable stock la cual devolvemos al final de la operación.

Finalmente en la operación AnadirStock, primero volvemos a usar la sentencia First de Linq para determinar si el id de producto existe en la base de datos. Seguidamente también usamos First para devolver el primer registro de la tabla inventario que cumpla con los criterios de producto id y bodega. Luego, al registro devuelto, en la columna stock, le sumamos la cantidad pasada por parámetro y finalmente salvamos los cambios en la base de datos por medio de SaveChanges del Entity Framework.

Ahora nos queda actualizar nuestra aplicación cliente para hacer una prueba de cada una de las operaciones. Primero necesitamos actualizar nuestra referencia para que el cliente conozca las nuevas operaciones y contrato de datos si los hay. Para esto nos vamos a nuestro proyecto consola  ProductoCliente y hacemos clic derecho sobre la referencia que teníamos previamente, en el menú emergente y elegimos “Update Service Reference



Ya con la referencia actualizada podemos trabajar en el código del cliente. El archivo Program.cs debería quedar más o menos asi:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using ProductoCliente.TestWCF;

namespace ProductoCliente
{
    class Program
    {
        static void Main(string[] args)
        {
            // Se crea el proxy para conectar con el servicio
            ProductoServicioClient proxy = new ProductoServicioClient();

            // Probamos el metodo que obtiene los id de productos
            Console.WriteLine("Probando ListaProductos: Listado de Id de Productos");
            List<string> ListaProductos = proxy.ListaProductos();
            foreach (string idProdcto in ListaProductos)
            {
                Console.WriteLine("Id Producto: {0}", idProdcto);
            }
            Console.WriteLine();

            Console.WriteLine("Probando DetalleProducto: Detalle de producto B-0011");
            ProductoDetalle detalle = proxy.DetalleProducto("B-0011");
            Console.WriteLine("Producto ID: {0}", detalle.ProductoId);
            Console.WriteLine("Descripción: {0}", detalle.Descripcion);
            Console.WriteLine("Precio: {0}", detalle.Precio.ToString());

            Console.WriteLine();

            Console.WriteLine("Probando StockProducto: Stock completo del producto B-0011");
            int stock = proxy.StockProducto("B-0011");
            Console.WriteLine("Stock Completo: {0}", stock.ToString());

            Console.WriteLine();


            Console.WriteLine("Probando AnadirStock: Colocando en la bodega 3 la cantidad de 30 más");
            if (proxy.AnadirStock ("B-0011", 3, 30))
            {
                stock = proxy.StockProducto("B-0011");
                Console.WriteLine("Stock Modificado. Nuevo Stock: {0}", stock);
            }
            else
            {
                Console.WriteLine("Actualización de stock fallido");
            }
            Console.WriteLine();

            // Desconectado del servicio
            proxy.Close();
            Console.WriteLine("Presione ENTER para terminar.");
            Console.ReadLine();
        }
    }
}

Como podemos ver la clase ProductoDetalle esta disponible y de hecho la utilizamos para mostrar el detalle de producto, también usamos StockProducto para verificar que AnadirStock funciona correctamente.

Antes de ejecutar el cliente la información en mi base de datos se ve más o menos así:




Ejecutamos nuestra aplicación cliente, la cual nos arroja el siguiente resultado:



Todo según el plan.

En el siguiente post pasaremos nuestro servicio al IIS, tal y como sería una vez finalizadas las pruebas de un desarrollo de este tipo.

Roy {aka. Foy}

Autor & Editor

Desarrallador y líder técnico, con experiencia en tecnologías Microsoft desde los tiempos del VB6 y el asp clásico hasta el .Net Core, pasando por COM+, javascript, angularjs, Ionic, xaml, cordova, MVC, Web Api, Sql Server, Oracle... . Ávido lector, apasionado programador.