17 de diciembre de 2014

Crear, hospedar(IIS) y consumir WCF Service App

WCF es la evolución de las tecnologías Web Service de Microsoft de años anteriores ya que provee un amplio rango de funcionalidad por encima de web services, con mejores características en aspectos de calidad como flexibilidad, portabilidad y mantenibilidad, también vale mencionar que hay clases que no se pueden serializar a través de un web service en .NET que si tienen soporte en WCF (HashTable por ejemplo).

Los servicios de WCF pueden funcionar bajo distintos protocolos, aunque el http es el mas común,ademas los servicios de WCF puede ser alojados en distintos host, no necesariamente tiene que ser el IIS, puede ser un Windows Service que desarrolles, o una aplicación de consola que este ejecutándose y exponga servicio, etc

WCF Service Application y WCF Service Library

Voy a poner explicita una imagen de una explicación que me pareció concisa para exponer cuales son las diferencias entre WCF Service App y WCF Service Library...


Par Interface-clase servicio WCF

Al momento de desarrollar un servicio WCF se debe tener en cuenta dos partes: una interface que representara el contrato de nuestra aplicación(me refiero a aquello que estas obligado a implementar)que se decora con [ServiceContract] y las operaciones a exponer que se decoran con el atributo [OperationContract], ademas se necesita una clase que debe implementar esa interfaz, aquí un ejemplo sencillo antes de continuar
[ServiceContract]
public interface ITest{  

[OperationContract]
string ShowMessage(string strMsg);
        }

public class Service : ITest{  
 public string ShowMessage(string strMsg)
  {
   return strMsg;
  }
    }

Para hacer el ejemplo interesante vamos a utilizar una base de datos con tres tablitas, recuerda que el ejemplo puede hacerse tan complejo o tan básico como tu lo necesites, valida la justificación anterior aquí esta mi humilde base de datos


Creando el servicio WCF

toca abrir el VS y crear un nuevo proyecto del tipo WCF: WCF Service Application, con nombre Host-Service, este agregara una interfaz y una clase que implementara la interfaz tal como muestro en la figura


lo que vamos a hacer ahora es eliminar los dos archivos que señalo en la figura y agregar un nuevo item al proyecto del tipo WCF Service


Agregando item con nombre Servicio-WCF, este archivo sera la clase que implementara nuestra interface y sera el archivo que se hospedara en nuestro servidor y que expondrá los servicios a las demás aplicaciones


Al agregar el item WCF Service con nombre Servicio-WCF se agregan dos archivos: una interface y con nombre IServicio-WCF y el archivo Servicio-WCF.svc, nuestro proyecto quedara de la siguiente manera visto desde Solution Explorer


Al crear un servicio de WCF, la primera tarea es definir un contrato de servicio, esto no es mas que una interface que especifica los métodos que brindara el WCF Service y que luego tendrán que ser implementados por una clase especifica La interfaz queda de la siguiente manera, se creara un método para mostrar la típica cadena de texto para mostrar un saludo, pero con eso no llegaremos lejos así que también vamos a crear un método para mostrar el estado de una conexión a SQL Server y un método para mostrar datos de una tabla utilizando Dataset(No es la mejor manera pero como primera aproximación ayudara bastante)

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

namespace App_WCF{
  
[ServiceContract]
 public interface IServicio_WCF{

[OperationContract]
string PruebaTexto(string valor);

[OperationContract]
string PruebaConexion();

[OperationContract]
DataSet PoblarDGV();
 
  }}
Ya tenemos la interface ahora toca codificar la clase que implementa esta interface, esta clase es Servicio-WCF.svc...antes de continuar quiero dejar claro que esta es una primera aproximación, puedes encontrar ejemplos en los que se cree un proyecto tipo WCF Service Library en los cuales la clase o bien clases(puedes utilizar las que quieras aquí el que manda eres tu) que implementa la o las interfaces(de nuevo, el que manda eres tu) son archivos con extensión cs, es decir, simples clases, por lo que no estas atado a utilizar el archivo con extensión svc como la clase que implementa la interfaz...mas adelante haré un ejemplo con WCF Service Library y veras las mínimas pero considerables diferencias, de momento sigamos, aquí esta el código que implementa la interface

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

namespace App_WCF{

public class Servicio_WCF : IServicio_WCF{
    
 public string PruebaTexto(string valor){
   string mensaje = string.Empty;
    try
       {
        mensaje = "Saludos " +valor+  " "+ "Mensaje de prueba desde el Servicio-WCF";
       }
    catch (Exception ex)
       {
        throw new ArgumentException(ex.Message);
       }
  return mensaje;
 }

public DataSet PoblarDGV(){
 string conexion = ConfigurationManager.ConnectionStrings["conexion"].ConnectionString.ToString();
 SqlConnection sql = new SqlConnection();
 sql.ConnectionString = conexion;
 DataTable dt = new DataTable();
 DataSet ds = new DataSet();
  try{
   using (sql){
    sql.Open();
    SqlDataAdapter da = new SqlDataAdapter("select * from empleado", sql);
    da.Fill(ds);
    return ds;                        
     }
    }
  catch (Exception ex){             
   throw new ArgumentException(ex.Message);
 }}          
        
public string PruebaConexion(){
 string mensaje = string.Empty;
 string conexion= ConfigurationManager.ConnectionStrings["conexion"].ConnectionString.ToString();
 SqlConnection sql= new SqlConnection();
 sql.ConnectionString= conexion;
            
 try{
  sql.Open();
  if (sql == null)
  {
  mensaje = "la conexion no tiene ningun valor";
  }
  else if (sql.State == ConnectionState.Closed)
  {
  mensaje = "la conexion no esta Open";
  }
  else
  {
  mensaje = sql.State.ToString(); ;
  }
  sql.Close();
  return mensaje;
  }
               
  catch (Exception ex) { return ex.Message; }
}}}

muestro ademas como queda mi web.Config, que pasa con los endpoint? que pasa con los serviceBehavior? es acaso que no voy a configurar nada? sigan leyendo...
<configuration>
  <connectionStrings>
      <add name="conexion"
        connectionString="Data Source=LINGONET\SQLEXPRESS;Initial Catalog=ejemplo;Integrated Security=SSPI"
        providerName="System.Data.SqlClient" />
  </connectionStrings>
    <system.web>
    <customErrors mode="Off"/>
    <authentication mode="Windows"/>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

Al utilizar WCF en nuestro web.Config deberemos especificar los endpoints pero en este ejemplo solo especificamos la cadena de conexión, no agregamos ningún endpoint, entonces como se espera que funcione el servicio WCF? facil, aprovechamos los Default Endpoints de WCF4, estos se agregaron porque la mayor queja de los programadores al utilizar WCF era la confusión que creaban los endpoints, era mas fácil codificar los métodos que exponía el servicio que crear un endpoint en el web.Config asi que se crearon los Default Endpoints.

estos son endpoints “preconfigurados” que se cargan automáticamente para cada dirección base creada. Al llamarse al método Open del ServiceHost, ya sea automáticamente por parte de IIS o manualmente cuando se hostea el servicio en una aplicación de consola, se crean estos bindings predeterminados haciendo uso del método AddDefaultEndpoints.

Un par de cuestiones a tener en cuenta:
1. Si se configura un endpoint en el fichero de configuración, ya no se hace efectiva la llamada a AddDefaultEndpoints.
2. Si aún así quisiéramos tener esos endpoints por defecto, siempre es posible llamar explícitamente al método AddDefaultEndpoints y añadir, a los endpoints definidos en el fichero de configuración, los que crea él por defecto.

Que nos ofrecen los default endpoints? una configuración basica por defecto que se considera como la más habitual con una dirección base con protocolo http, el binding por defecto es basicHttpBinding. Esta nueva técnica ha sido bautizada como Default Protocol Mapping. En cualquier caso, este mapeo también es configurable; si quisiéramos que por defecto la direcciones con protocolo se resolvieran con un binding de tipo wsHttpBinding, sería necesario agregar lo siguiente en el app.config/web.config:

  <system.serviceModel>
    <protocolMapping>
      <add scheme="http" binding="wsHttpBinding"/>
    </protocolMapping>
  </system.serviceModel>

OJO: en otra entrada de este blog mostrare como crear los endpoint ya que si eres un programador de respeto no te vas a quedar con los default endpoint, porque los utilizo aquí? porque es una introducción...recuerdas? ademas imagino no te hara daño el saber que existen y que si eres un principiante no tendrás que preocuparte(de momento) de los endpoints al crear tu primer servicio WCF.

Nos quedaría activar los metadatos del servicio para generar el fichero WSDL y enviar información detallada en caso de error. Ambas no son configuraciones por defecto del servicio, por lo que tendríamos que editar el fichero app.config/web.config e introducir lo siguiente:

<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

Si nos fijamos, veremos que no ha sido necesario ni darle un nombre al nuevo behavior del servicio, ni tampoco definir el servicio y asociarle el behavior, como hacíamos en el framework3.5. Esto se conoce como Default Behavior Configurations, configuraciones sin nombre que se van a aplicar a cualquier servicio que definamos. Tienen su contrapartida en los Default Binding Configurations, que realizan la misma función pero asociados a un tipo de binding. y listo, damos F5 y probamos los métodos, como ejemplo pruebo el método PruebaConexion()


Hospedar el servicio WCF

Ahora viene la parte de hospedar el servicio WCF, una de las ventajas es que no es obligaion que los hospedes en IIS, pero como nos quedamos con los default endpoints que utilizan una direccion base http, entonces hospedemolo en IIS, recuerda verificar los siguiente para el transporte de datos

<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

ahora debemos verificar que el IIS esta debidamente instalado, para probarlo escriban localhost en la barra de direcciones de su browser y deberían tener esta pantalla...la imagen que les aparecerá dependerá de la versión de IIS que tengan instalado


ahora si damos click derecho sobre el servicio que creamos anteriormente y escogemos la opción View in Browser tal como muestro en la figura


Se abrirá el explorador determinado mostrando el servicio


Notar la dirección web que muestra, esta utilizando el IIS Express es por eso que muestra el Localhost:1997, el numero 1997 representa el puerto que asigna el IIS Express, lo que buscamos es hospedar el servicio en IIS y que simplemente la dirección web sea Localhost...manos a la obra, lo que viene es abrir el administrador de IIS, aquí lo que vamos a verificar sera el Application Pool, abrimos el IIS Manager y seleccionamos Application Pool, debemos tener instalado el framework 4.0


y damos click en Advanced Settings al lado derecho del panel, en esta ventana de configuraciones  deben cambiar el Identity por LocalSystem y verificar que la version del framework sea 4.0 tal como muestro en la figura (en caso no ser así recuerden que deben tener instalado el Framework 4.0)


Terminamos de configurar el IIS para hospedar nuestro servicio WCF, nada dificil verdad?...ahora regresemos a nuestro proyecto y sobre Servicio-WCF.svc damos click derecho y seleccionamos propiedades

y en este panel nos vamos a la opción web y cambiamos el IIS Express por Local IIS y seguidamente damos click en el botón Create Virtual Directory


ahora solo debemos regresar a nuestra aplicación, dar click derecho en el servicio y escoger View in Browser y...lo logramos, vean la direccion web y notaran la diferencia, ahora es solamente http://localhost/App-WCF/Servicio-WCF.svc lo que significa que esta instalado en nuestro servidor local y no utiliza un puerto temporal



Por ultimo vamos al IIS manager y podemos ver que en nuestra carpeta Sites se ha agregado el servicio App-WCF


Consumiendo el servicio WCF

Procedemos a crear un nuevo proyecto(muy aparte y nada que ver con el proyecto en que creamos el servicio WCF), crearemos una aplicación de Windows Forms aunque bien hubieramos tenido la opcion de utilizar WPF, WebForms o MVC.

como complemento, para comunicacion entre aplicaciones .NET se deberia utilizar un endpoint utilizando el binding="netTcpBinding" ademas muestro econnectionString del web.config del servicio WCF, es importante que Integrated Security = SSPI


Ahora en nuestro proyecto tendremos un WinForm con este diseño, lo hice así para que se adapte a la información que entregara el servicio WCF asi que ustedes disculparan la simplicidad


ahora viene lo importante, se va agregar una referencia del servicio WCF que acabamos de crear al proyecto de Windows Forms, como lo hacemos? debemos copiar la dirección web que se obtiene al dar click derecho sobre el servicio y escoger la opción View in browser para luego agregar la referencia de servicio tal como se muestra en la secuencia



en el Namespace cambie el ServiceReference1 por Servicio, asi la manera de invocar al servicio WCF sera 
Servicio.Servicio_WCFClient test = new Servicio.Servicio_WCFClient();


a continuación muestro el código del formulario para el evento button1_click, en este se invocara a los métodos públicos del servicio y se mostraran los resultados en los dos label y en el DataGridView1 tal como se muestra:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;

namespace WindowsFormsApplication1{
    public partial class Form1 : Form{
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e){
            Servicio.Servicio_WCFClient test = new Servicio.Servicio_WCFClient();
            string valor = textBox1.Text;
            label1.Text = test.PruebaTexto(valor);
            label2.Text = test.PruebaConexion();
            DataSet ds = new DataSet();
            ds = test.PoblarDGV();
            dataGridView1.DataSource=ds.Tables[0];
        }
    }
}
si, si...todo lo anterior esta bien para una primera aproximación, me atrevo a decir que tesaldra a la primera, pero hagamos algo mas interesante, algo como realizar operaciones sobre una base de datos, utilizar procedimientos almacenados y de paso serializar una clase, lo expongo en la entrada CRUD consumiendo un servicio WCF