jueves, 18 de noviembre de 2010

Ejemplo de Servicios WCF... en C#

Ejemplo del uso de servicios WCF (mini ejemplo)

Hace unos cuantos meses,  un ex compañero de clases tenía que resolver un pequeño problemita, el cual era que no encontraba la manera de realizar una  aplicación que funcionara de manera distribuida.
Al elegir .NET como la plataforma de desarrollo, específicamente el lenguaje C#, tenía muchas opciones y herramientas, podría hacerlo con remoting, servicios web, MSMQ, etc. Pero en .NET Framework 3.0 se incluyen algunas mejoras, como WCF (además de WPF, WWF y CardSpace), WCF nos brinda un modelo estándar de programación que integra todas estas tecnologías, Así de esta manera se pueden crear servicios de una forma rápida y sobre todo adaptable.

Requisitos:
·         Framework 3.0 o superior
·         Visual Studio 2008 “Orcas” o con el Visual Studio 2005 y las extensiones para el .NET Framework 3.0

Creación BD
La idea es que nuestro servicio será independiente de la forma en la cual nos comunicaremos con él.
El proceso de creación de un servicio se podría dividir de la siguiente forma:
  1. Definición y creación de un contrato, en el cual definiremos la funcionalidad (métodos accesible) que tendrá el servicio y los datos (clases, estructuras, etc) utilizara para comunicarse.
  2. Crear el servicio que implemente el contrato definido.

En este ejemplo utilizaremos una tabla creada en Access donde iremos almacenando nombres de personas.
La BD pueden nombrarla como deseen, la tabla a fines de muestra se llama Personas y tiene los siguientes campos:

Campo
Tipo
Id
Autonumerico
ApPaterno
Texto
ApMaterno
Texto
Nombre
Texto


La estructura de los proyectos será la siguiente:





  • El proyecto AccesoDatos contiene la clase de manipulación de la BD.
  • El proyecto FrontEnd es el formulario desde el cual consumiremos el servicio.
  • Listados contiene una clase que interviene en la comunicación, la cual debe ser serializada.
  • Negocios es donde se define el servicio.

A continuación definimos la clase para manipular la pequeña BD

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Odbc;
using Listados;
namespace AccesoDatos
{
    public class PersonasAD
    {
        #region conexion
        string constring = "Driver={Microsoft Access Driver (*.mdb)};DBQ=D:/Demo/BD/Personas.mdb;UID=;PWD=";
        OdbcConnection conexionbase;
        OdbcCommand comandos;
        System.Data.Odbc.OdbcDataReader reader;
        #endregion

        #region consulta
        public List<Personas> ListadoGeneral(out string msgError)
        {
            msgError = string.Empty;
            List<Personas> retorno = new List<Personas>();

            using (conexionbase = new OdbcConnection(constring))
            {
                comandos = new OdbcCommand("SELECT * FROM Personas",conexionbase);

                try
                {
                    conexionbase.Open();
                    reader = comandos.ExecuteReader();

                    while (reader.Read())
                    {
                        Personas pers = new Personas();
                        pers.Id =(int) reader[0];
                        pers.ApPat =(string) reader[1];
                        pers.ApMat =(string) reader[2];
                        pers.Nombre = (string)reader[3];
                        retorno.Add(pers);
                        pers = null;
                        //reader.NextResult();
                    }
                }
                catch (OdbcException ex)
                {
                    msgError = ex.Message;
                }
            }

            return retorno;
        }
        #endregion

        #region insertar
        public void AgregaPersona(string appat, string apmat, string nombre, out string msgerror)
        {
            msgerror = string.Empty;
            using (conexionbase = new OdbcConnection(constring))
            {
                comandos = new OdbcCommand("INSERT INTO Personas (ApPaterno,ApMaterno,Nombre) VALUES (?,?,?)",conexionbase);
                comandos.Parameters.Add("@ApPaterno", OdbcType.Text).Value = appat;
                comandos.Parameters.Add("@ApMaterno", OdbcType.Text).Value = apmat;
                comandos.Parameters.Add("@Nombre", OdbcType.Text).Value = nombre;

                try
                {
                    conexionbase.Open();
                    comandos.ExecuteNonQuery();
                }
                catch (OdbcException ex)
                {
                    msgerror = ex.Message;
                }
            }
        }
        #endregion
    }
}

Este clase se compone de dos métodos, el primero devuelve una Lista de tipo Personas, conteniendo todos los registros que se encuentran en la tabla de Access.
El otro método es el encargado de realizar las inserciones de registros en la tabla.

Montando el Servicio

A continuación se procederá a crear las clases para definir el contrato.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Listados;
using System.ServiceModel;

namespace Negocios
{
    [ServiceContract]
    public interface IPersonas
    {
        [OperationContract]
        List<Personas> ListadoGeneral(out string msgerror);

        [OperationContract]
        void InsertarReg(string app, string apm, string nombre);
    }
}

Creamos el proyecto llamado Negocios y a continuación creamos la interfaz a la cual llamaremos IPersonas. Recordar que para trabajar con WCF tener que añadir una referencias al proyecto y hacer el using a la librería “System.ServiceModel”

Como se habran dado cuenta, para indicar que se trata de un contrato utilizamos el atributo [ServiceContract] al momento de declarar la interface, luego para definir que métodos tendrá el contrato usamos el atributo [OperationContract].
Se pueden declarar mas métodos en la interfaz, pero si deseamos que sean publicados en el contrato tenemos que utilizar el atributito mencionado.
Este contrato es el que usaran los clientes para saber que operaciones tiene el servicio y que datos interviene en la comunicación. Para este caso utilizaremos la clase llamada Personas que estará contenida en el proyecto Listados (recordar hacer la referencia en el proyecto de Negocios)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace Listados
{
    [DataContract]
    public class Personas
    {
        int id;
        string appat, apmat, nombre;
       
        [DataMember]
        public int Id
        {
            get { return id; }
            set { id = value; }
        }

        [DataMember]
        public string ApPat
        {
            get { return appat; }
            set { appat = value; }
        }

       [DataMember]
        public string ApMat
        {
            get { return apmat; }
            set { apmat = value; }
        }

      [DataMember]
        public string Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }
    }
}

Para indicar que la clase también debe estar en el contrato utilizaremos los atributos DataContract y DataMember, para la definición de la clase y de sus propiedades respectivamente. Recordar que este clase debe de poder ser seriarizable (para poder ser enviado por XML y SOAP si lo deseamos).

Creando el Servicio
Ahora queda por hacer es implementar la interfaz (el contrato) y crear la clase que dará el servicio.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Listados;
using AccesoDatos;

namespace Negocios
{
    public class PersonasLN:IPersonas
    {
        PersonasAD personasad;
        public List<Personas> ListadoGeneral(out string msgerror)
        {
            personasad = new PersonasAD();
            return personasad.ListadoGeneral(out msgerror);
                       
        }

        public void InsertarReg(string app, string apm, string nombre)
        {
            personasad = new PersonasAD();
            string msgError = "";
            try { personasad.AgregaPersona(app, apm, nombre, out msgError); }
            catch (Exception ex) { msgError = ex.Message; }
        }
    }
}

Hemos creado la clase que implementa la interfaz que define el contrato.
Ya se tiene definido el servicio y lo que tenemos que decidir es como se va a acceder a el: ya sea a travez de SOA y XML, mediante HTTP, TCP, etc y el que lo va a servir; IIS, un servicio del SO, otro programa, etc.

La ventaja de utilizar WCF es que esto lo hacemos aparte de la definición del contrato y del servicio, de esta manera se separa la funcionalidad de lo que es las comunicaciones.
Cabe mencionar que la definición de las comunicaciones se hacen mediante ficheros de configuración basados en XML. Así que se algún dia deseamos cambiar un servicio que sirve como un servicio web a un servicio que sirva por TCP por el puerto 1234, solo habrá que cambiar dos líneas del fichero de configuracion y listo, sin tener que modificar el código del servicio.

Configurando el servicio

Lo primero será servir el servicio WCF creado anteriormente como un servicio web a travez de SOAP y XML. Para esto debemos instalar el IIS (Panel de control->Programas y características->Agregar o quitar características de Windows->Buscar casilla que diga Internet Information Services).
Por defecto las dll´s de los proyectos de VS se generan en la carpeta bin\debug, pero al poner el servicio en IIS éste las lee directamente de bin, así que tenemos que cambiar el lugar de generación. para hacerlo, botón derecho sobre el proyecto(Negocios)->Propiedades->en el apartado Build en output cambiamos la ruta  a bin/, guardamos.
Luego creamos un fichero de configuración para configurar el servicio y sus endpoints. Como la aplicación va a estar servida por IIS, la configuración va dentro del archivo web.config (click derecho sobre el proyecto->Agregar->Nuevo elemento->en nombre poner web.config).
A continuación el web.config para el servicio que hemos creado:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Negocios.PersonasLN">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="Negocios.IPersonas"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>

Aquí hemos definido un solo servicio con solo en endpoint, pero podríamos tener varios servicios. En el atributo name del servicio se ha colocado el espacio de nombres del servicio seguido del servicio. Después le hemos dicho que contrato implementa el servicio.

Lo único que nos falta es un fichero con extencion .svc para que el usuario tenga un punto a donde navegar y el IIS sepa que lo que se esta sirviendo es un servicio WCF y sepa como compilar y genera la información WSDL del contrato cuando el cliente lo pida.
Añadimos un fichero de texto y lo nombremos Servicio.svc
<%@ ServiceHost Service="Negocios.PersonasLN"%>

En el fichero indica que servicio tiene que compilar y ejecutar.

Con esto ya se puede poner el servicio en IIS y navegar para poder verlo. En IIS creamos un nuevo directorio virtual que apunte al directorio raíz donde se ubica el fichero .svc y listos (recordar dar permiso de lectura a la carpeta donde se ubica).

Accedemos al servicio con nuestro navegador preferido (Mozilla Firefox): http://localhost/DemoWCF/Servicio.svc, y obtendremos algo como esto:
 




SI ponemos atención hay una línea que indica: Metadata publishing for this service is currently disabled. Por seguridad IIS deshabilita la posibilidad de publicar la metadata. Si deseamos verla y que clientes de fuera puedan acceder a ella y poder ver el WSDL, debemos modificar el web.config.
Creamos un behavior que extenderá la funcionalidad de nuestro servicio
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="MiBehavior" name="Negocios.PersonasLN">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="Negocios.IPersonas"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MiBehavior">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>
Si cargamos la pagina nuevamente, veremos que si podemos acceder a la metadata que genera a partir del contrato (WSDL), donde se define que funcionalidades da nuestro servicio, que tipos de datos utiliza y como se accede a el.

Con esto ya tenemos funcionando nuestro servicio web.




 
Creando un cliente

el cliente será una aplicación Windows forms, con el siguiente diseño:



la forma mas rápida de crear un cliente/consumidor de un servicio WCF es con la ayuda de Visual Studio, la forma de hacerlo es muy parecida a como lo hacíamos cuando se trataba de un servicio web del framework 2.0.
En nuestro proyecto(FrontEnd) donde queremos consumir el servicio tenemos la opción de Add Service Reference , pero en este caso la dirección que indicaremos no solo tiene por que ser http, también podemos introducir diferentes protocolos, net.tcp//:, net.pipe//:, net.msmq//:, etc.
Click derecho en service references->agregar referencia de servicio->introducir la dirección donde se esta sirviendo(en el ejemplo: http://localhost/DemoWCF/servicio.svc)


Creamos una nueva instancia de la clase “PersonasClient “(recordar que en WCF en el nombre original de la clase + Client) y podremos utilizar los métodos que expone.
Por ejemplo, a continuación el código del botón que inserta un nuevo registro

        private void button2_Click(object sender, EventArgs e)
        {
            string err = string.Empty;
            new PersonasClient().InsertarReg(textBox2.Text, textBox3.Text, textBox4.Text);//, out err);
            if (!string.IsNullOrEmpty(err))
            {
                MessageBox.Show(err,"Error");
            }
            else
            {
                LlenaTabla();
                MessageBox.Show("Registro Guardado","Okis");
            }
        }
Si en algún momento, queremos exponer el servicio a travez de TCP por el puerto 1234 solo tenemos que configurar elo archivo web.config,
Como verán implementar aplicaciones, con tecnología WCF es de una forma sencilla, aunque recordar que faltaría implementarle seguridad, para decidir quienes pueden acceder al servicio y quienes nop.

Cualquier comentario, aclaración, duda no duden en comentarlo
J
Saludos……

El código fuente lo pueden encontrar en: http://bit.ly/9fa754

5 comentarios:

  1. Gracias por la ayuda. Podrías subir algún ejemplo de WPF. Te lo agradeceria. Estoy empezando con .NET

    ResponderEliminar
  2. Hola Hola estamos empezando a trabajar en WCF y nos han pedido un login pero a decir verdad no tengo idea de como hacer la navegación entre paginas, podrias ayudarme o subir algún ejemplo Gracias!! :D

    ResponderEliminar
  3. y si la consulta trae mas de 70 mil registros que debo hacer para que el servicio no genere error . ?

    ResponderEliminar
  4. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  5. con esta codigo ya no te traera error:
    try
    {
    var myBinding = new BasicHttpBinding();
    myBinding.Security.Mode = BasicHttpSecurityMode.None;
    myBinding.MaxReceivedMessageSize = 100000000;
    var myEndpointAddress = new EndpointAddress(VGlobales.WCF_URL);

    WSDINW4W.W4WClient WSServicio = new WSDINW4W.W4WClient(myBinding, myEndpointAddress);

    WSDINW4W.ArticuloRequest oCarga = new WSDINW4W.ArticuloRequest();
    oCarga.DataSource = VGlobales.BD;
    oCarga.PageNo = VGlobales.PAGENO;
    oCarga.PageSize = VGlobales.PAGESIZE;
    oCarga.Password = VGlobales.BDClave;
    oCarga.UserId = VGlobales.BDUsuario;
    oCarga.bKit = nKit;
    oCarga.sCodCia = VGlobales.CODCIA;
    oCarga.sCiaVenta = VGlobales.CIAVENTA;
    oCarga.sAlmacen = sAlmacen;
    oCarga.sCodItem = sCodItem;
    oCarga.sDescItem = sDescItem;
    var sResultado = WSServicio.ArticuloListar(oCarga);
    DGArticulos.DataSource = sResultado.ListaArticulo;
    }
    catch (Exception e)
    {
    Console.WriteLine(e.Message);
    }

    ResponderEliminar