24 oct 2012

WCF 4 Parte 5: Diferentes Host para nuestro servicio

Continuamos con las notas del libro “Windows Communication Fundation 4”. En los post anteriores creamos un servicio WCF, creamos un cliente que lo probara, le añadimos nuevas operaciones y finalmente lo publicamos en un IIS. En esta nueva entrada vamos explorar nuevas formas de alojar nuestro servicio de WCF.

Ahora bien, que nuestro servicio este alojado (host)  en el IIS es una excelente alternativa si los clientes o servicios que lo utilizarán lo harán por medio de internet utilizando http o https, por ejemplo un proveedor o cualquier agente externo a nuestra organización. Pero cuando nuestros clientes se encuentran dentro de los alcances de nuestra intranet, hay mejores alternativas.


Windows Process Activation Service (WAS)

Comencemos con la utilización del Windows Process Activation Service (WAS). Este extiende la funcionalidad del IIS removiendo la dependencia del protocolo HTTP. Utilizando WAS para alojar nuestros servicios WCF podemos hacer uso de protocolos como el TCP, name pipes, MMQ (colas). Estando en este punto es importante recordar una de las máximas del WCF: la definición e implementación del contrato y del servicio son casi completamente independientes del ambiente en que se aloja o del protocolo de comunicación, quedando éstos como detalles de configuración.

Instalemos pues el WAS. Desde el panel de controles elegimos “Programs and Features” luego “Turn Windows Features on or off”, en la pantalla que se nos presenta seleccionados “Windows Process Activation Services” y todas sub-dependencias, también seleccionamos “Windows .Net Framework 3.5.1” y todas sus dependencias (.Net 3.5.1 contiene características necesarias para ejecutar WCF dentro de WAS)



Una vez realizado esto debemos asegurarnos de volver a registrar el .Net Framework 4.0, para esto nos vamos a la ventana de comandos de Visual Studio 2010 como administrador y ejecutamos la instrucción “aspnet_regiis -iru” sin comillas.

Ahora bien ejecutamos el “Internet  Information Services (IIS) Manager” como Administrador, una vez aquí hacemos clic derecho sobre “Default Web Site” y en el menú emergente hacemos clic sobre sobre “Edit Bindings…”. Si el WAS fue instalado correctamente la pantalla que se nos presentará será similar a la siguiente:



Podemos elegir alguno de los bindings y editar sus características, por ejemplo en net.tcp podemos añadir más puertos por los cuales escuchar peticiones o cambiar el puerto por defecto (808). De momento dejamos la configuración por defecto y hacemos clic en el botón Close.

Ahora seleccionamos el sitio correspondiente a nuestro servicio WCF y en el panel “Actions” elegimos “Advanced Settings…”, editamos la propiedad “Enabled Protocols” para añadir net.tcp, esto se hace separando con coma los protocolos:



Hacemos clic en el botón OK. Ya tenemos configurado nuestro servicio escuchando solicitudes tanto por medio HTTP (en el IIS) como por TCP (en el WAS). Ahora configuremos nuestra aplicación cliente para que se comunique con el servicio por medio de TCP.

Dentro de Visual Studio en la solución en la que hemos venido trabajando elegimos el proyecto que contiene nuestra consola cliente y abrimos el archivo App.config. Nos ubicamos en la sección de client para añadir un nuevo endpoint quedando de la siguiente manera:
<client>
  <endpoint address="http://localhost/ProductoServicio/Service.svc"
      binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IProductoServicio"
      contract="TestWCF.IProductoServicio" name="BasicHttpBinding_IProductoServicio" />

  <endpoint address="net.tcp://localhost/ProductoServicio/Service.svc"
      binding="netTcpBinding" contract="TestWCF.IProductoServicio"
      name="NetTcpBinding_IProductoServicio" />
</client>
Como se ve, el primer endpoint es el que ya teníamos, el que usamos para comunicarnos con el servicio en el IIS utilizando HTTP, el segundo es el que recién añadimos: la ruta es net.tcp y el binding es netTcpBinding, este binding esta parte del juego de bindings predefinidas en el WCF runtime.

Si ejecutamos el cliente sin mayor modificación nos dará un error tipo InvalidOperationException en el que se nos indica que nuestro contrato tiene más de un endpoint válido.  Para solventar esta situación lo que necesitamos es utilizar el método sobrecargado encargado de crear el Proxy para que sepa cual endpoint utilizar.  Abrimos el archivo program.cs y cambiamos la línea de creación del proxy de manera que quede de la siguiente manera:
// Se crea el proxy para conectar con el servicio
ProductoServicioClient proxy = new ProductoServicioClient("NetTcpBinding_IProductoServicio");
Ahora si ejecutamos nuestra aplicación cliente el resultado debe ser el mismo que cuando nos conectábamos al IIS por HTTP solo que ahora sería al WAS vía TCP:

Este tipo de implementación del servicio flexibiliza su consumo, de manera que en una sola ubicación el servicio puede responder tanto a peticiones externas a la organización via http o https como a las peticiones internas vía TCP, lo cual es más eficiente en el escenario interno.

Aplicación Windows

Segunda opción para alojar nuestro servicio para consumo interno: una Aplicación Windows. Para lograr este acople entre nuestro servicio y una aplicación debemos comenzar reconociendo que nuestro servicio no puede ser un sitio web, debemos implementarlo como WCF Service Library.

Comencemos, en nuestra solución añadimos un nuevo proyecto, en la ventana que se nos presenta en la sección de Templates elegimos WCF y WCF Service Library, cambiamos el nombre por uno más significativo (En mi caso ProductoServicioLibrary)

Eliminamos los archivos IService.cs y Service1.cs, seguidamente hacemos clic derecho sobre nuestro nuevo proyecto y, del menú emergente, seleccionamos Add -> Exisitng Item..  y elegimos nuestro contrato e implementación del sitio web que habíamos construido previamente y hacemos clic en el botón Add.

Hay que añadir además la referencia a nuestro proyecto de modelos de datos (en este ejemplo ModeloProductos) y la referencia del System.Data.Entity para finalizar el establecimiento de nuestro servicio. Como podemos deducir, el código que escribimos para el sitio web es casi exactamente el mismo que vamos a utilizar en este nuevo “host”, solo debemos eliminar esta línea de código:
using System.ServiceModel.Web;
Ahora necesitamos una aplicación Windows que nos sirva de alojo para nuestro servicio.  En nuestra solución añadimos un nuevo proyecto, este será un WPF Application. En mi caso yo le pondré ProductoServicioHost:

Ahora en nuestro recien creado proyecto, necesitamos renombrar el archivo MainWindow.xaml por HostServicio.xaml (en una app WPF recordemos que este es el archivo que contiene la ventana o el form principal). Abrimos el archivo App.xaml y cambiamos el atributo StartupUri para que apunte a HostController.xaml:
<Application x:Class="ProductoServicioHost.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="HostServicio.xaml">
    <Application.Resources>
         
    </Application.Resources>
</Application>
Ahora modificamos la ventana de la aplicación de la siguiente manera:  hacemos doble clic en HostServicio.xaml para aceder al diseñador y al codigo XAML procurando que quede más o menos de la siguiente forma.

Ahora vamos a trabajar en el código. Primero debemos añadir la referencia al assembly System.ServiceModel y una más a nuestro proyecto de WCF Library (en mi caso a ProductoServicioLibrary) Una vez añadidas estas referencias procedemos a modificar el código para que quede de la siguiente manera:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ServiceModel;
using TestWCF;

namespace ProductoServicioHost
{
    /// <summary>
    /// Interaction logic for HostServicio.xaml
    /// </summary>
    public partial class HostServicio : Window
    {
        private ServiceHost servicioHost;

        public HostServicio()
        {
            InitializeComponent();
        }

        private void Deterner_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                servicioHost.Close();
                btnDetener.IsEnabled = false;
                btnIniciar.IsEnabled = true;
                txtEstado.Text = "Detenido";
            }
            catch (Exception ex)
            {
                ManejoExcepcion(ex);
            }
        }
        
        private void Iniciar_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                servicioHost = new ServiceHost(typeof(ProductoServicio));
                servicioHost.Open();
                btnDetener.IsEnabled = true;
                btnIniciar.IsEnabled = false;
                txtEstado.Text = "En Ejecución";
            }
            catch (Exception ex)
            {                
                ManejoExcepcion (ex);
            }
        }

        private void ManejoExcepcion(Exception e)
        {
            MessageBox.Show(e.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }
}
Expliquemos. Añadimos una variable privada de tipo ServiceHost que es la que posibilita el alojamiento del servicio. Luego en el botón iniciar instanciamos nuestro servicio dentro del host y lo “abrimos” de manera que escuche las peticiones. Por el contrario, en el botón Detener “cerramos” el host con lo que no se procesarán mas solicitudes. Adicionalmente se crea una función utilitaría para el manejo de las excepciones.

Ahora necesitamos agregar la configuración del endpoint. Esto es algo que cuando desarrollamos el servicio para el IIS o para el WAS de lado del servidor era muy transparente. Pero en esta nueva situación debemos manejarlo de forma diferente.

Primer añadimos un Archivo de configuracion, App.config, a nuestro Proyecto (ProductoServicioHost) [Add New Item -> Application Configuration File] seguidamente le damos clic derecho sobre el mismo y le damos  Edit WCF Configuration:

Esto abrirá una nueva aplicación por medio de la cual podemos configurar nuestro servicio:

A continuación le damos cli en el link de la derecha “Create a New Service” una vez abierta la siguiente pantalla le damos clic al boton “Browse…” y nos vamos hasta la carpeta Bin -> Debug y escogemos la dll correspondiente a nuestro proyecto WCF Library (ProductoServicioLbrary.dll en mi caso) y clic en Open, seguidamente escogemos la implementación de nuestro servicio y de nuevo clic en Open.

Hacemos clic en Next. En la pantalla siguiente dejamos la especificación del contrato tal y como esta y le damos clic en Next. Nos aparecerá la pantalla para elegir el modo de comunicación en el cual elegimos TCP:

Hacemos clic en Next. En esta pantalla debemos colocar la dirección del endpoint. La dirección que utilizaremos para el ejemplo será net.tcp://localhost:8080/TcpService  

Hacemos clic en Next. Se nos presenta un resumen y el botón para finalizar esta parte de la configuración.

Luego de dar clic en Finish aún nos queda hacer una última cosa. Del lado derecho expandimos la carpeta Endpoints, seleccionamos el nodo “(Empty Name)” y del lado izquierdo cambiamos la propiedad Name para que nos quede de acuerdo al estándar (en mi caso sería NetTcpBinding_IProductoServicio)

Hacemos clic en File -> Save y cerramos la ventana. Seguidamente hacemos doble clic sobre el App.config y le añadimos luego de la apertura del tag <configuration> la cadena de conexión a la base de datos (esta lo podemos copiar del archivo de configuración del proyecto de modelo datos). Finalmente el archivo debe quedar similar a lo siguiente:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
      <add name="TestDBEntities" connectionString="metadata=res://*/ModeloProductos.csdl|res://*/ModeloProductos.ssdl|res://*/ModeloProductos.msl;provider=System.Data.SqlClient;provider connection string="Data Source=FOYPC\FOYSERVER;Initial Catalog=TestDB;Integrated Security=True;MultipleActiveResultSets=True;Application Name=EntityFramework"" providerName="System.Data.EntityClient" />
    </connectionStrings>  
    <system.serviceModel>
        <services>
            <service name="TestWCF.ProductoServicio">
                <endpoint address="net.tcp://localhost:8080/TcpService" binding="netTcpBinding"
                    bindingConfiguration="" name="NetTcpBinding_IProductoServicio"
                    contract="TestWCF.IProductoServicio" />
            </service>
        </services>
    </system.serviceModel>
</configuration>
¡Listo! Ahora solo necesitamos probar todo lo creado, esto lo podemos realizar por medio de nuestra consola de pruebas. Vamos a utilizar el mismo endpoint que utilizamos para probar el servicio en WAS. Lo que vamos a hacer es abrir el archivo app.config de nuestra consola de pruebas y cambiar la dirección del endpoint de la siguiente manera:
<endpoint address="net.tcp://localhost:8080/TcpService"
      binding="netTcpBinding" contract="TestWCF.IProductoServicio"
      name="NetTcpBinding_IProductoServicio" />
Cambiamos un poquito el código para que nos de tiempo de levantar el servicio primero por lo que al principio de nuestra consola añadimos las siguientes dos líneas de código justo antes de instanciar el proxy:
Console.Write("Presione Enter cuando el servicio se encuentre en ejecución.");
Console.ReadLine();

// Se crea el proxy para conectar con el servicio
ProductoServicioClient proxy = new ProductoServicioClient("NetTcpBinding_IProductoServicio")
Ahora habilitemos el inicio de múltiples proyectos para levantar tanto la consola de pruebas como nuestra aplicación WPF que aloja nuestro servicio. Para esto damos clic derecho sobre la solución y seleccionamos la opción Properties del menú emergente. Del lado izquierdo seleccionamos “Starup Project” y del lado derecho hacemos clic en el radiobutton Multiple Startup Projects, seguidamente seleccionamos Start en Action de los dos proyectos que necesitamos inicien.

Ejecutamos.

Iniciamos el servicio en el host y luego damos en enter en la aplicación Cliente.


Como podemos apreciar todo funciona igual a como funcionaba con el servicio alojado el IIS o el WAS, sólo que ahora lo hace desde una aplicación WPF.

Servicio Windows

Ahora finalmente y como tercera opción alojemos nuestro servicio WCF en un servicio Windows. Como fácilmente se puede deducirse al alojar un WCF en una aplicación como en el ejemplo dependería de un usuario el iniciar el servicio y detenerlo. Una forma común de resolver este inconveniente es alojar el WCF en un servicio Windows que inicia automáticamente con el sistema. Se Puede consultar la entrada Construyendo un Servicio Windows para más claridad ya que iremos un poco más rápido en este tópico.

Primeramente creamos nuestro proyecto de servicio Windows. Clic derecho sobre la solución luego Add ->New Project…->Windows Service (se encuentra en la opción Windows del panel de plantillas) yo nombre como ProudctosServicioWindows.

Una vez generado el proyecto renombramos el archivo service1.cs por ServicioHost.cs y hacemos clic en “Yes” cuando se nos pregunte por renombrar todas las referencias. Ahora añadir las siguientes referencias System.ServiceModel, System.Runtime.Serialization y System.Data.Entity. Ademas añadimos la referencia a nuestro proyecto de Modelado de datos (ModeloProductos en mi caso).

Ahora para ahorrarnos el digitar el código, vamos a copiar los archivos del proyecto WCF Library que habíamos hecho antes (ProductoServicioLibrary). De ahí copiamos los archivos IProductoServicio.cs, ProductoServicio.cs y también vamos agregar el archivo app.config de nuestra aplicación de WPF anterior (ProductoServicioHost).

Ahora vamos al servicio Windows para añadir el código necesario para controlar el servicio WCF. Hacemos clic derecho sobre el archivo ServicioHost.cs y elegimos View Code. El código final debe verse similar al siguiente:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.ServiceModel;
using TestWCF;

namespace ProudctosServicioWindows
{
    public partial class ServicioHost : ServiceBase
    {
        //Variable que mantiene la instancia del servicio WCF
        private ServiceHost ProductosHost;
        public ServicioHost()
        {
            InitializeComponent();
            //Nombre del servicio
            this.ServiceName = "ProductosServicio";
            //Permitir al administrador detenero o reiniciarlo
            this.CanStop = true;
            //usar el event log de windows para los procesos de inicio y detencion del servicio
            this.AutoLog = true;
        }

        protected override void OnStart(string[] args)
        {
            ProductosHost = new ServiceHost(typeof(ProductoServicio));
            ProductosHost.Open();
        }

        protected override void OnStop()
        {
            ProductosHost.Close();
        }
    }
}
Ahora añadimos un instalador a nuestro servicio. Hacemos doble clci sobre el archivo ServicioHost.cs para activar la vista de diseño, sobre la misma hacemos clci derecho y elegimos Add Installer

Hacemos clic en el componente serviceInstaller1 recien creado y en su ventana de propiedades configuramos el nombre del servicio y cambiamos el valor de la propiedad startType a Automatic.

 Finalmente seleccionamos el componente ServiceProcessIntaller1 y en la ventana de propiedades cambiamos el valor de Account a LocalSystem y compilamos la solución.

Ahora instalemos el servicio. Abrimos el Visual Studio Command Prompt (2010) y nos vamos a la carpeta en la que creamos el servicio Windows hasta la carpeta Bin/Debug de la misma. Ahí ejecutamos la siguiente instrucción.
installutil ProudctosServicioWindows.exe
Este comando instalará el servicio. Ahora podemos a hasta la consola de servicios Windows e iniciamos nuestro servicio. Clic derecho el servicio y hacemos clic sobre Start.

Con nuestro servicio ejecutándose y sin hacer ningún cambio en nuestra consola de pruebas podemos probar que todo esté bien.

Podemos ver que se comporta exactamente igual que en las demás implementaciones. Para estar seguro que estamos usando el servicio Windows podemos detenerlo y ejecutar de nuevo la consola de pruebas, obtendríamos un error similar al siguiente:

Como hemos visto en estas notas, existen varias posibilidades para alojar nuestro servicio WCF, más de las aquí expuestas. Lo importante es recordar la independencia de la implementación del servicio propiamente dicha de su consumo o alojamiento.

Hasta aquí queda esta serie de notas, cerrando con el post más extenso que he realizado hasta ahora…