Mostrando entradas con la etiqueta Windows Service. Mostrar todas las entradas
Mostrando entradas con la etiqueta Windows Service. Mostrar todas las entradas

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…


8 jun 2011

Construyendo un servicio Windows

Este es un pequeño manual para construir servicios Windows, con recomendaciones de mi propia cosecha sin que esto se acerque a mejores prácticas o algo similar, simplemente anotaciones que para mi son útiles.
Primeramente creamos un proyecto de tipo C# -> Windows y usamos la plantilla Servicio Windows

Luego eliminamos la clase Service1.cs y elegimos agregar un nuevo elemento, y elegimos Servicio de Windows y ponemos el nombre que queremos, en mi caso igual al proyecto TestService

Una vez hecho esto vamos a la clase Program.cs que es de donde realmente se arranca el servicio y don de se instancia el servicio cambiamos Service1 por el nombre que hallamos elegido para nuestro servicio

static void Main()
{
	ServiceBase[] ServicesToRun;
	ServicesToRun = new ServiceBase[];
	{
  		new TestService()
	};
	ServiceBase.Run(ServicesToRun);
}

Ahora bien deseo que mi Servicio Windows tenga algunas llaves parametrizables e instrumentación. Entonces el siguiente paso es agregar un archivo de configuración. Al respecto recordar que el archivo de configuración debe llamarse igual que el servicio más las extensiones ”.exe.config“ o sea, que en nuestro ejemplo seria TestService.exe.config. Para lograr esto añadimos un nuevo elemento tipo Archivo de Configuración de Aplicación

Por cuestiones de requisitos, el webservice debe tener una hora de primera ejecución y luego un intervalo de tiempo. Para esto añadimos dos llaves en el archivo de configuración que acabamos de crear.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key ="HoraEjecucion" value="08:00:00"/>
<add key="FrecuenciaEjecucion" value="30"/>
</appSettings>
</configuration>

La primera hora de ejecución será a la 8 de la mañana y a partir de ahí se ejecutara cada 30 minutos. Ahora necesitamos utilizar estas llaves en nuestra aplicación. Lo primero a tener en cuenta es que si el servicio se inicia a las 5:00 todavía faltaría 3 horas para que el servicio comience a trabajar. Una de las formas de lograr esto es utilizando un Timer, leemos la llave del archivo de configuración las llaves, calculamos el tiempo que falta la primera ejecución, lo colocamos en el intervalo del mismo y lo enlazamos con el evento principal del servicio.

Primero necesitamos tener acceso a las llaves del archivo, entonces añadimos una referencia a la librería de .Net System.configuration y la utilizamos usando dentro de la clase del servicio utilizando el using


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.Configuration;
namespace TestService
{
 partial class TestService : ServiceBase
 {
    System.Timers.Timer timer = new System.Timers.Timer();
    public TestService()
    {
        InitializeComponent();
        timer.Enabled = false;
        timer.Elapsed += (evento);
    }

    protected override void OnStart(string[] args)
    {
        PrimeraEjecucion();
    }

    protected override void OnStop()
    {
       /*TODO: agregar código aquí para realizar cualquier anulación necesaria para detener el servicio.*/
    }

    private void evento(object sender, System.Timers.ElapsedEventArgs e)
    {
        MetodoPrincipal();
    }

    protected void MetodoPrincipal()
    {
        timer.Enabled = false;
        /*TODO: Logica Principal del Servicio*/
        SiguienteEjecucion();
    }

    protected void PrimeraEjecucion()
    {
        TimeSpan HoraInicio = TimeSpan.Parse(ConfigurationManager.AppSettings["HoraEjecucion"]);

        TimeSpan TiempoEspera = (System.DateTime.Now.TimeOfDay.Subtract(HoraInicio).Ticks < 0) ?
                 System.DateTime.Now.TimeOfDay.Subtract(HoraInicio).Negate() :
                 HoraInicio.Add(new TimeSpan(1, 0, 0, 0)).Subtract(System.DateTime.Now.TimeOfDay);

        ActualizaTimer(TiempoEspera.TotalMilliseconds);
    }

    protected void SiguienteEjecucion()
    {
        double minutos = Convert.ToDouble(ConfigurationManager.AppSettings["FrecuenciaDeEjecucion"]);
        ActualizaTimer(TimeSpan.FromMinutes(minutos).TotalMilliseconds);
    }

    protected void ActualizaTimer(double milisegundos)
    {
        timer.Enabled = false;
        timer.Interval = milisegundos;
        timer.Enabled = true;
    }
 }
}

La explicación del código de arriba es la siguiente:

Primero creamos el timer de forma global. En la inicialización del componente deshabilitados el timer y, por medio del delegate, le asignamos un evento que apunta al método principal del webservices. Luego en el onStart del servicio llamamos al método PrimeraEjecucion Este se encarga de leer la llave correspondiente a la hora de la primera ejecución del archivo de configuración y calcular cuando hace falta para que esta se alcance (TiempoEspera), lo convierte a milisegundos y con esto llama al método ActualizaTimer que se encarga de añadir el tiempo al Timer y habilitarlo. Con esto conseguimos que una vez que se alcance el tiempo que falta para la primera ejecución el método principal del servicio se ejecute. Finalmente en el método Principal lo primero que hacemos es deshabilitar el timer para no incurrir en llamadas traslapadas si el tiempo de espera para la primera ejecución es muy corto, y al final del mismo llamamos al método SiguienteEjecucion que el intervalo de tiempo de las siguientes ejecuciones del método principal y se lo pasa a ActualizaTimer.

El método SiguienteEjecucion lo podemos modificar un poco de manera que por ejemplo reciba un boolean para saber si la siguiente ejecución es normal o no, previendo que si hay un error en la ejecución del método principal la siguiente ejecución sea en un tiempo distinto a que si terminara de modo normal. Es solo una idea que podríamos implementar.

Ahora queremos implementar la instrumentación, en su faceta de seguimiento o reporte de errores, básicamente habilitar el tracing. Hay varias formas de hacerlo, pero para este caso decidí hacerlo por medio del archivo de configuración usando System.Diagnostics.

Inmediatamente luego de cerrar la sección AppSetttings incluimos la siguiente sección

<system.diagnostics>
<trace autoflush ="true" />
<sources>
 <source name="SourcePrueba" switchName="SwicthPrueba" switchType="System.Diagnostics.SourceSwitch">
   <listeners>
     <clear/>
     <add name="evtlogPrueba"
       type="System.Diagnostics.EventLogTraceListener"
       initializeData="PruebaServicio" />
     <add name="textwriterListener"
       type="System.Diagnostics.TextWriterTraceListener"
       initializeData="Log_ServicioPrueba.txt"
       traceOutputOptions="ProcessId, DateTime, Callstack" />
   </listeners>
 </source>
</sources>
<switches>
 <add name="SwicthPrueba" value="Verbose"/>
</switches>
</system.diagnostics>

Con esto hacemos varias cosas autoflush significa que conforme registremos eventos inmediatamente se incluirán en sus respectivos destinos. El source será lo que instanciamos en la aplicación para implementar el tracing. Este source, a su vez, utiliza el switch especificado para determinar que se va a registrar; esto lo veremos más claramente más adelante. Además incluimos dos listeners uno que es un eventlog y otro que es un archivo de texto. En el que es de tipo eventlog el initializeData se refiere al source del eventlog. Finalmente definimos un switch el cual por, medio de su value, determinara que se registrará cuando usemos el TraceSource en la aplicación.

Quizás todo lo anterior es un poco confuso ya que es la mera configuración del tracing. Ahora lo veremos como utilizarlo dentro de nuestro servicio. Primeramente instanciamos un TraceSouce de manera global (en la misma área que instanciamos el timer)

TraceSource ts = new TraceSource("SourcePrueba");

Como vemos lo instanciamos utilizando como parámetro el mismo nombre que usamos para el source que definimos en la configuración. Ahora vamos ha utilizarlo en varias partes de nuestro código. Por ejemplo vamos a registrar un evento cuando el servicio se inicie y cuando se detenga, modificando los respectivos métodos de la siguiente manera.

protected override void OnStart(string[] args)
{
	ts.TraceEvent(TraceEventType.Information,0,"Servicio Test Iniciado");
	PrimeraEjecucion();
}

protected override void OnStop()
{
	ts.TraceEvent(TraceEventType.Information,0,"Servicio Test Finalizado");
}

El método principal lo vamos a modificar de la siguiente manera para probar como funciona el switch del archivo de configuración al final del presente manual.

protected void MetodoPrincipal()
{
	timer.Enabled = false;
	ts.TraceEvent(TraceEventType.Information, 0, "Servicio Test: Informacion");
	ts.TraceEvent(TraceEventType.Error, 0, "Servicio Test: Error");
	ts.TraceEvent(TraceEventType.Warning, 0, "Servicio Test: Alerta");
	ts.TraceEvent(TraceEventType.Verbose, 0, "Servicio Test; General/Siguimiento");
	/*TODO: Logica Principal del Servicio*/
	SiguienteEjecucion();
} 

Prácticamente hemos finalizado, la creación de nuestro servicio de Prueba. Sin embargo nos falta prepararlo para poder instalarlo. Primero necesitamos añadirle un instalador a nuestro proyecto. Vamos a la clase TestService.cs, pero no al código si no al diseñador, en éste hacemos clic derecho y elegimos Agregar Instalador.

Elegimos la nueva clase que se nos ha añadido ProjectInstaller.cs en su diseñador y escogemos el componente ServiceInstaller1 nos vamos a propiedades y cambiamos algunas propiedades

Como se aprecia, podemos cambiar la descripción del servicio y el nombre que mostrará en la consola de servicios de windows. Así como si quedará con activación automática o manual. Lo configuramos según nos parezca.

Finalmente debemos decidir con que cuenta se ejecutará el servicio para esto seleccionados el serviceProcessInstaller y en sus propiedades buscamos el elemento Account. Se seleccionamos según se desee (Por lo general los servicios se ejecutaran con LocalSystem)

Ahora vamos a crear dos archivos .bat para instalación y desinstalación de nuestro servicio. Esto es muy cómodo por ejemplo, cuando necesitamos pasarlo a otro departamento para su instalación. Estos archivos se puede crear utilizando notepad y luego de guardarlo simplemente le cambiamos la extensión de .txt a .bat

El archivo Instalador.bat quedaría como sigue:

@ECHO OFF

REM Estos es para usar con el Framework .NET 2.0

set DOTNETFX2=%SystemRoot%\Microsoft.NET\Framework\v2.0.50727

set PATH=%PATH%;%DOTNETFX2%

echo Instalando Servicio de Prueba...

echo ---------------------------------------------------

InstallUtil /i TestService.exe

echo ---------------------------------------------------

echo Instalacion Finalizada.

echo ---------------------------------------------------

pause

Y el Desinstalar.bat así:

@ECHO OFF

REM Estos es para usar con el Framework.NET 2.0

set DOTNETFX2=%SystemRoot%\Microsoft.NET\Framework\v2.0.50727

set PATH=%PATH%;%DOTNETFX2%

NET STOP "TestService"

echo Desinstalando Servicio de Prueba...

echo ---------------------------------------------------

InstallUtil /u TestService.exe

echo ---------------------------------------------------

echo Hecho

pause

Nos asegurarmos que todo compila, movemos los archivos que necesitamos a una carpeta aparte para realizar nuestras pruebas.

Comencemos ejecutando el archivo Instalador.bat . Si al seleccionar la cuenta del servicio le indicamos user en lugar de LocalSystem, por poner un ejemplo, probablemente nos pida el usuarioy su respectiva contraseña para ejecutar el servicio Windows en la máquina

Vamos a la consola de servicios para verificar que efectivamente se instaló el servicio.

Antes de iniciar el servicio debemos asegurarnos que si vamos a utilizar un eventlog personalizado este debe estar creado y asociado con el source que indicamos en el archivo de configuración previamente. Si no lo hacemos se creará el source, pero asociado con el eventlog default que suele ser el de aplicación. Una vez hecho esta verificación si fuese el caso, lo iniciamos.

Una vez iniciado, si queremos depurarlo lo que hacemos es asociar el proceso desde Visual Studio. Depurar -> Asociar el Proceso, eligiendo en la pantalla que se nos muestra el proceso en cuestión.

Una vez asociado ya podemos depurar

Una vez ejecutado el método principal del servicio verificamos que los eventos se registraron tanto en el archivo como en el eventlog:

Ahora bien, anteriormente no quedó muy claro para que sirve el switch que definimos en el archivo de configuración, pues bien en este momento se están registrando todo los eventos que definidos en el TraceSource porque este switch tiene el valor Verbose, que significa que registre todo, si queremos que únicamente los errores se registren, es tan simple como cambiar el valor del swicth en el archivo de configuración y reiniciar el Servicio, sin alterar en lo mas mínimo nuestra programación. Para probarlo realizamos el siguiente cambio en el archivo de configuración

<add name ="SwicthPrueba" value="Error"/>

Detenemos el servicio, borramos las entradas del eventlog y el archivo. Iniciamos de nuevo el servicio y procedemos a verificar que ahora únicamente se registran los errores.

Esto es todo de momento, hasta aquí este manual de cómo crear un servicio Windows.