Servicios en .NET

ForTutorial NET

Veremos que es, para que puede servir, como desplegar-lo y debugarlo. Lo veremos a través de un sencillo ejemplo que mueve ficheros de un directorio a otro cada segundo. Lo veremos en VB.NET y C#.NET

¿Que es?

Un servicio no deja de ser una aplicación sin interfaz gráfica que se lanza cada X tiempo.

¿Para que sirve?

Sirve por si queremos que cada x minutos haga algún proceso.
Por ejemplo, si tenemos que en nuestra aplicación se generen logs, en lugar de intentar enviar el log justo en el momento, puede interesarnos que se genere un registro en una BDD o que genere un fichero xml y que nos envíe todos los errores cada 30 segundos.

Otro ejemplo podría ser que en el servidor, una vez cada hora compruebe si hay actualizaciones y se las descargue.

Otro ejemplo más, sería la posibilidad de adjuntar información a la BDD, si nos llega una información por xml ( nos llega un alta de un coche por xml y tenemos un servicio que cada 20 segundos los introduce en la BDD )

Como veis puede llegar a ser bastante útil y tiene muchas posibilidades.

Empezamos

Crearemos un servicio muy sencillo que mueva ficheros, en mi caso lo uso para historificar documentos adjuntos de unas operaciones.

Primero, tenemos que crear un proyecto de tipo servicio, para ello le damos a New Project y le damos a Windows Service, le ponemos un nombre y una ubicación. Aquí vemos que hay una sola clase (la llamaremos svr), esta será donde pongamos las llamadas a funciones(clsFunciones) y el thread que lo recorre. Aparte de esta clase le pondremos otras: una clase de instalación (installSvr), una que contendrá la lógica del negocio (clsFunciones) y un directorio «Xml» que contendrá las configuraciones que queramos.

Xml de configuración

En el proyecto crearemos un directorio xml ( es únicamente por organización ) y crearemos un xml que se llame config.xml ( por ejemplo ), este contendrá las configuraciones del servicio como el nombre y descripción del servicio, directorios que necesitemos, etc.

Por ejemplo, una posible estructura sería:

Quitar los espacios de los <, si no, no se veían

<configuraciones>
	<configuracion>
		<nombre>NombreServicio</nombre>
		<valor>Servicio de pruebas</valor>
		<descripcion>El nombre del servicio que se mostrará en la consola de servicios de windows</descripcion>
	</configuracion>
	<configuracion>
		<nombre>DescripcionServicio
		<valor>Servicio que se ocupa de historificar los ficheros
		<descripcion>La descripción del servicio que se mostrará en la consola de servicios de windows
	</configuracion>
	<configuracion>
		<nombre>DirectorioLog</nombre>
		<valor>D:Logs</valor>
		<descripcion>El directorio donde están los logs</descripcion>
	</configuracion>
	<configuracion>
		<nombre>DirectorioFicheros</nombre>
		<valor>Servicio de pruebas</valor>
		<descripcion>El directorio donde están los ficheros que queremos historificar</descripcion>
	</configuracion>
	<configuracion>
		DirectorioHistorico</nombre>
		D:File2</valor>
		El directorio donde estarán los ficheros que queremos historificar</descripcion>
	</configuracion>
</configuraciones>
La clase clsFunciones

Esta clase será la que contendrá lo que queremos hacer realmente (copiar ficheros, introducir datos, …) y la ponemos en una clase aparte para tenerlo bien separado.
Como hemos dicho, este será un servicio que mueva ficheros, así que aquí pondremos las funciones necesarias para ello.

Crearemos una función para hacer las pruebas:
VB.NET

    Public Function historificarFicheros() As Boolean
        Try
            '' dameConfig es una funcion que busca en el xml una configuración
            Dim strDirFiles As String = dameConfig("DirectorioFicheros")
            Dim strDirHistorificar As String = dameConfig("DirectorioHistorico")

            If System.IO.Directory.Exists(strDirFiles) Then
                Dim Files As String() = IO.Directory.GetFiles(strDirFiles) 'obtengo todos los ficheros del directorio
                For Each strFile As String In Files
                    If IO.Directory.Exists(strDirHistorificar) Then
                        Dim strNombre As String = strFile.Split(""(0)).Last() 'obtengo el nombre del fichero
                        If IO.File.Exists(strFile) Then IO.File.Move(strFile, strDirHistorificar & "" & strNombre)
                    End If
                Next
                Return True
            Else
                ''LOG
            End If

            Return False
        Catch ex As Exception
            ''LOG
            Return False
        End Try
    End Function

C#.NET

 
public bool historificarFicheros()
        {
            try
            {
                // dameConfig es una funcion que busca en el xml una configuración
                String strDirFiles = dameConfig("DirectorioFicheros");
                String strDirHistorificar = dameConfig("DirectorioHistorico");

                if (System.IO.Directory.Exists(strDirFiles))
                {
                    String[] Files = System.IO.Directory.GetFiles(strDirFiles); //obtengo todos los ficheros del directorio
                    foreach (String strFile in Files)
                    {
                        if (System.IO.Directory.Exists(strDirHistorificar))
                        {
                            String strNombre = strFile.Split('\').Last(); //obtengo el nombre del fichero
                            if (System.IO.File.Exists(strFile)) System.IO.File.Move(strFile, strDirHistorificar + "\" + strNombre);
                        }
                    }
                    return true;
                }
                else
                {
                    //LOG
                }
                return false;
            }
            catch (Exception)
            {
                //LOG
                return false;
                throw;
            }
        }
La clase «svr»

Esta es la clase de la sesión, aquí indicaremos lo que hará y cada cuanto. Ponemos las siguientes variables que son para que cada x tiempo lance una función

VB.NET

Private WithEvents temporizador As Timers.Timer
  Private intervalo As Integer = 1000

C#.NET

private System.Timers.Timer temporizador;
private Integer intervalo=1000;

Sobrescribimos el constructor del servicio para que se pueda pausar, continuar y que se inicie directamente.

VB.NET

    Public Sub New()
        MyBase.New()
        InitializeComponent()
        CanPauseAndContinue = True
        OnStart(Nothing)
    End Sub

C#.NET

public svr()
        {
            InitializeComponent();
            CanPauseAndContinue = true;
            OnStart(null);
        }

Después sobrescribimos los métodos de start, stop y continue. Estos eventos se provocan en la consola de servicios de windows cuando inicias, paras, reseteas …

VB.NET

    Protected Overrides Sub OnStart(ByVal args() As String)
        temporizador = New Timers.Timer(intervalo) 'le indicamos cada cuanto tiene que hacer su funcion
        temporizador.Start() 'iniciamos el timer"
    End Sub

    Protected Overrides Sub OnStop()
        temporizador.Stop()
    End Sub

    Protected Overrides Sub OnContinue()
        temporizador.Start()
    End Sub

C#.NET

protected override void OnStart(string[] args)
        {
            temporizador = new System.Timers.Timer(intervalo); //le indicamos cada cuanto tiene que hacer su funcion
            temporizador.Start(); //iniciamos el timer
        }
        protected override void OnStop()
        {
            temporizador.Stop();
        }
        protected override void OnContinue()
        {
            temporizador.Start();
        }

Y por último capturamos el evento del timer que se lanza cuando pasa el tiempo

VB.NET

Private Sub temporizador_Elapsed(ByVal sender As Object, _
    ByVal e As System.Timers.ElapsedEventArgs) _
    Handles temporizador.Elapsed
        Try
            ' Se deshabilita temporalmente el timer
            temporizador.Enabled = False
            Dim histFiles As New clsFunciones
            histFiles.historificarFicheros()
        Catch ex As Exception
            ''LOG
        Finally
            temporizador = New Timers.Timer(intervalo) '' se reinicia el timer
            temporizador.Enabled = True
        End Try
    End Sub

C#.NET

    private void temporizador_Elapsed(Object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                // Se deshabilita temporalmente el timer

                temporizador.Enabled = false;
                clsFunciones histFiles = new clsFunciones();
                histFiles.historificarFicheros();

            }
            catch (Exception)
            {
                //LOG
                throw;
            }
            finally
            {
                temporizador = new System.Timers.Timer(intervalo); // se reinicia el timer
                temporizador.Enabled = true;
            }
        }
La clase installSvr

Esta clase es necesaria para que despleguemos el servicio, cuando lo instalemos irá a consultar esta clase. Para añadirla, solo tenemos que agregar un nuevo ítem de tipo InstallerClass.

Insertamos los Imports/Usings de System.ComponentModel, System.Configuration.Install y System.ServiceProcess, si no los tenemos.

Después, os tenéis que fijar que herede de System.Configuration.Install.Installer, si no haced que herede poniéndole Inherits o «:»

Añadimos 2 propiedades

C#.NET

  Private ServiceInstaller serviceInstaller1
  Private ServiceProcessInstaller processInstaller

VB.NET

 Private serviceInstaller1 As ServiceInstaller
  Private processInstaller As ServiceProcessInstaller

Y Modificamos el constructor para que quede así, voy comentando en el código:
VB.NET

  Public Sub New()
        MyBase.New()

        InitializeComponent()

        Try
            ' Instanciamos el proceso y el servicio de instalación
            processInstaller = New ServiceProcessInstaller()
            serviceInstaller1 = New ServiceInstaller()
            ' Le ponemos los permisos que tendrá
            processInstaller.Account = ServiceAccount.NetworkService

            ' Indicamos si queremos que sera automatico el arranque del servicio ( cuando se inicia Windows)
            serviceInstaller1.StartType = ServiceStartMode.Automatic
            ' ServiceName must equal those on ServiceBase derived classes.

            serviceInstaller1.ServiceName = "Nombre de Nuestro Servicio"
            serviceInstaller1.Description = "Descripcion del Servicio"

            ' Se añade el servicio y proceso a la instalación.
            Installers.Add(serviceInstaller1)
            Installers.Add(processInstaller)
        Catch ex As Exception

            ' Esto nos aparecera al intalarlo, se instala a través del cmd, así que allí nos aparecerá el error
            Console.Out.WriteLine("ERROR EN LA INSTALACION!!!!" &amp; ex.Message)
        End Try

    End Sub

C#.NET

 public Installer1()
        {
            InitializeComponent();

            try
            {
                //Instanciamos el proceso y el servicio de instalación
                processInstaller = new ServiceProcessInstaller();
                serviceInstaller1 = new ServiceInstaller();
                // Le ponemos los permisos que tendrá
                processInstaller.Account = ServiceAccount.NetworkService;

                // Indicamos si queremos que sera automatico el arranque del servicio ( cuando se inicia Windows)
                serviceInstaller1.StartType = ServiceStartMode.Automatic;
                // ServiceName must equal those on ServiceBase derived classes.

                serviceInstaller1.ServiceName = "Nombre de Nuestro Servicio";
                serviceInstaller1.Description = "Descripcion del Servicio";

                // Se añade el servicio y proceso a la instalación.
                Installers.Add(serviceInstaller1);
                Installers.Add(processInstaller);
            }
            catch (Exception ex)
            {

                // Esto nos aparecera al intalarlo, se instala a través del cmd, así que allí nos aparecerá el error
                Console.Out.WriteLine("ERROR EN LA INSTALACION!!!!" + ex.Message);
                throw;
            }
        }

Instalar un servicio.

Ahora que lo tenemos creado, solo faltaría que funcionase, para ello tenemos que instalarlo con una herramienta que esta en el propio .NET y el cmd. La herramienta se llama InstallUtil.exe y esta en C:WindowsMicrosoft.NETFrameworkv4.0.30319InstallUtil.exe. En mi caso el proyecto esta con el framework 4 y por eso esta en el directorio v4. Una vez localizado el exe, lo copiamos y pegamos en el directorio donde esta el servicio, recomiendo no compilar en el mismo directorio.

¿Por que copiarlo y pegarlo en este directorio?
[spoiler]Es porque normalmente tendremos ficheros de configuración que estar con el .exe. Si no lo hiciésemos y quisiéramos saber el directorio actual en la instalación, nos daría el directorio del framework y tendríamos que tener allí los ficheros de configuración[/spoiler]

Después nos vamos al cmd y vamos a la ruta donde esta el servicio con el InstallUtil.exe, y escribimos «InstallUtill.exe Servicio.exe» ( Servicio.exe sera el exe que crea el servicio)

Debugg

Si queremos debugar el servicio solo nos haría falta adjuntar el proceso al VS. Para hacerlo solo tendríamos que ir a tools/attach process( si no nos aparece tendremos que añadir el botón a través de agregar o quitar botones, en la parte de debug estará attach process), nos aparece una lista, seleccionamos el nuestro y ya estaría, si está en marcha y hay algún punto de debug saltará automáticamente; si está parado, ejecutamos el proyecto en el VS. Recomiendo tener parado el servicio para debugar e ir nosotros a ejecutarlo.