Desenvolvimento - ASP. NET

Por dentro da Base Classe Library - Capítulo 11 - Criando Serviços do Windows

Os Serviços do Windows (Windows Services), permitem-nos criar aplicações que rodam em “background” no sistema operacional. Estes serviços podem ser automaticamente inicializados quando o sistema operacional inicializar, podendo ainda ser pausado e reinicializado, sem apresentar nenhuma interface com o usuário. Esses serviços são ideais para ser usado em servidores ou em funcionalidades de longa duração que necessitem ser executadas de forma totalmente independente, sem a intervenção de um usuário. O capítulo corrente abordará desde a sua criação, depuração e instalação do mesmo.

por Israel Aéce



Introdução

Há situações onde precisamos ter sistemas que rodam independentemente da interação de um usuário. Deixar a aplicação a cargo do usuário executá-la de tempo em tempo podemos ter um problema mais sério, já que o usuário pode esquecer de executá-la e, conseqüentemente, uma determinada tarefa deixa de ser executada.

Os serviços do Windows (Windows Services) permitem executarmos uma tarefa de forma automática, sem a intervenção humana e sem a necessidade de ter um usuário logado na máquina, podendo inclusive serem inicializados automaticamente quando o sistema operacional entrar no ar. Esses serviços são executados em background, sem que o usuário perceba que o processo está em execução e são ideais para a construção de serviços que exigem um processo de longa duração ou mesmo tarefas periódicas. Esses serviços não possuem interface gráfica, apenas ferramentas do próprio Windows (ou customizadas também através do .NET) para que você possa interagir com o mesmo.

Uma das finalidades desta capítulo é apresentar o namespace System.ServiceProcess (contido dentro do Assembly System.ServiceProcess.dll), que fornece as classes e tipos necessários para a construção destes serviços. Além disso, iremos aprender como proceder para a construção, depuração e instalação de um serviço do Windows.

Service Control Manager – SCM

Uma serviço Windows não pode ser simplesmente executado. Ele precisa de um ambiente para isso, que o execute de forma automática e segura. É neste momento que entra em cena o Service Control Manager – SCM.

O SCM permite-nos interagir com um serviço do Windows, ou seja, podemos inicializar, parar e até mesmo executadr comandos em serviços contidos em uma determinada máquina. Além da interface gráfica que o Windows fornece para manipularmos os serviços, dentro do .NET Framework também temos uma classe chamada de ServiceController que permite, via código, interagirmos com os serviços.

Criando um serviço do Windows

O Visual Studio .NET fornece uma template de projeto, chamada de Windows Service, que podemos utilizar para a criação de um serviço do Windows. Ao criar esse tipo de projeto através do Visual Studio .NET, uma serviço padrão é adicionado ao projeto e, um método chamado Main é criado. Esse método é o ponto de entrada das aplicações .NET e, no caso do serviço do Windows, tem a finalidade que de inicializar o serviço através do método estático Run da classe ServiceBase.

O Assembly gerado por esse tipo de projeto será do EXE, podendo conter dentro dele vários serviços. Se repararmos, cada serviço que é criado dentro do projeto, herda diretamente de uma classe base chamada ServiceBase. Essa classe é a base para todos os serviços que serão hospedados dentro do Windows.

A classe ServiceBase possui uma porção de membros importantes que merecem serem descritos. Através das tabelas abaixo, podemos analisar as principais propriedades e métodos, repectivamente:

Propriedade

Descrição

AutoLog

Esta propriedade recebe um valor booleano indicando se os comandos Start, Stop, Pause e Continue serão logados dentro do Event Log do Windows.

CanHandlePowerEvent

Recebe um valor booleano indicando se o serviço poderá ou não receber notificações de mudança do status de energia.

CanHandleSessionChangeEvent

Recebe um valor booleano indicando se o serviço pode capturar eventos de mudança de sessão de um Terminal Server.

CanPauseAndContinue

Valor booleano que indica se o serviço pode ser pausado e, em seguida, continuado.

CanShutdown

Valor booleano que indca se o serviço deve ser notificado quando o sistema operacional estiver em processo de shutting down.

CanStop

Indica se o serviço poderá ser parado.

EventLog

Recebe uma instância da classe EventLog que o serviço do Windows utilizará para escrever as notificações de Start, Stop, Pause e Continue.

Será utilizado a seção Application do Event Log para armazenar essas informações.

ExitCode

Define um valor inteiro que é utilizado para reportar algum erro para o SCM.

ServiceName

Recebe uma string contendo o nome que identificará o serviço para o Service Control Manager (SCM).

Método

Descrição

OnContinue

Quando implementado na classe derivada, esse método é executado quando o comando Continue é enviado pelo SCM para o serviço. Geralmente ocorre quando você opta por continuar a execução de um serviço que está atualmente pausado.

É importante dizer que se a propriedade CanPauseAndContinue estiver definida com False, o SCM nunca notificará o serviço e, conseqüentemente, o método OnContinue nunca será invocado.

OnCustomCommand

Quando implementado na classe derivada, esse método é executado quando o SCM passa um comando customizado para o serviço, permitindo assim, especificar uma funcionalidade adicional ao serviço.

Comandos customizados são passado a partir do método ExecuteCommand da classe ServiceController, qual será abordada mais tarde, ainda neste capítulo.

OnPause

Quando implementado na classe derivada, esse método é executado quando o comando Pause é enviado pelo SCM para o serviço.

É importante dizer que se a propriedade CanPauseAndContinue estiver definida com False, o SCM nunca notificará o serviço e, conseqüentemente, o método OnPause nunca será invocado.

OnPowerEvent

Quando implementado na classe derivada, esse método é executado quando o status de energia é alterado. Isso geralmente é aplicado a notebooks.

É importante dizer que se a propriedade CanHandlePowerEvent estiver definida com False, o SCM nunca notificará o serviço e, conseqüentemente, o método OnPowerEvent nunca será invocado.

OnSessionChange

Quando implementado na classe derivada, esse método é executado quando o evento é recebido através de uma sessão de Terminal Server.

É importante dizer que se a propriedade CanHandleSessionChangeEvent estiver definida com False, o SCM nunca notificará o serviço e, conseqüentemente, o método OnSessionChange nunca será invocado.

OnShutdown

Quando implementado na classe derivada, esse método é executado quando o sistema está em processo de shutting down.

É importante dizer que se a propriedade CanShutdown estiver definida com False, o SCM nunca notificará o serviço e, conseqüentemente, o método OnShutdown nunca será invocado.

OnStart

Quando implementado na classe derivada, esse método é executado quando o comando Start é enviado pelo SCM para o serviço ou quando o sistema operacional é inicializado (desde que o serviço esteja definido para executar automaticamente).

A implementação desse método é sempre esperado para que o serviço seja útil e tenha um funcionamento adequado.

OnStop

Quando implementado na classe derivada, esse método é executado quando o comando Stop é enviado pelo SCM para o serviço.

Quando a propriedade CanStop é definida como False, o SCM ignora o comando Stop e, conseqüentemente, não o passa para o serviço. A implementação desse método é sempre esperado para que o serviço seja útil e tenha um funcionamento adequado.

Run (estático)

Trata-se de um método estático que tem dois overloads. Um deles recebe uma instância de um objeto do tipo ServiceBase; já o segundo overload, recebe um array de elementos do tipo ServiceBase.

Geralmente esse método é chamado dentro do método Main da aplicação Windows Service para servir como ponto de inicialização para o(s) serviço(s). Em seguida, esse mesmo método carrega o(s) serviço(s) para a memória e somente inicializará efetivamente o(s) serviço(s) quando o comando Start for passado pelo SCM.

Vimos que podemos inicializar, pausar ou parar um determinado serviço. Mas como devemos proceder para automatizar as tarefas dentro dos serviços? Exemplo: queremos que ele gerencie um determinado recurso ou periódicamente execute alguma tarefa?

Agora, tudo é por conta dos desenvolvedores, ou seja, chegou o momento de escrevermos código dentro do serviço. Esse código terá todo o processo para a execução das tarefas que ele deverá desempenhar.

Quando precisamos periódicamente executar uma ou várias tarefas, é muito comum utilizarmos dentro dos serviços do Windows o objeto Timer, que está contido dentro do namespace  System.Timers. Esse objeto, dado um intervalo (em milisegundos), ele executa infinitamente (ou até o serviço ser parado) e, quando o intervalo é alcançado, um evento chamado Elapsed é disparado, que é exatamente onde você deverá colocar o código a ser executado. Além disso, em outros cenários, podemos utilizar watchs, como é o caso da objeto FileSystemWatcher, contido dentro do namespace System.IO. Esse objeto monitora um determinado local físico do sistema de arquivos e, quando alguma mudança acontecer, ele detecta e dispara um evento que, você pode utilizar para efetuar algum processamento. Para maiores detalhes sobre este objeto, consulte o Capítulo 5 – Manipulando o sistema de arquivos.

Retomando o projeto de Windows Service, temos para cada serviço do Windows dois arquivos relacionados. Eles compõem uma única classe, ambos os arquivos contém uma mesma classe, mas utilizando o conceito das partial classes que, depois de compilado, transforma-se em apenas uma única classe. A tabela abaixo descreve a finalidade de cada um dos arquivos:

Arquivo

Descrição

Service1.Designer.vb

Service1.Designer.cs

Este arquivo contém a herança da classe base ServiceBase. Além disso, possui todas as informações a nível de inicialização do serviço e também uma parte visual (já que herda indiretamente da classe Component, contido no namespace System.ComponentModel), onde você pode arrastar controles (componentes) para ele.

Service1.vb

Service1.cs

Este arquivo, é onde implementamos os códigos de cada um dos eventos que vimos acima.

Para exemplificarmos, a vamos criar um serviço chamado PeopleServices.Service1, onde iremos adicionar um objeto do tipo Timer para escrevermos a hora atual em um arquivo a cada dez segundos (10000 milisegundos). Para manter a utilidade das partial classes, criaremos o objeto Timer dentro do arquivo (classe) Service1.Designer e lá também iremos criar a instância da mesma. Não devemos esquecer de vincular o procedimento que será executado quando o evento Elapsed for disparado e, para isso, utilizaremos o conceito de vinculação dinâmica de eventos.

O Timer possui dois métodos chamados Start e Stop que, são auto-explicativos. Quando o serviço do Windows for inicializado, devemos iniciar o Timer e, quando o serviço for parado, devemos parar o Timer. Sendo assim, chamaremos cada um desses métodos nos métodos OnStart e OnStop do serviço do Windows e, dentro do evento Elapsed, o código que executa a escrita no arquivo texto será executado. O código abaixo mostra como efetuar essa configuração que acabamos de descrever nos dois arquivos, apenas poupando algumas seções por questões de espaço:

VB.NET – Service1.Designer.vb

Imports System.Timers

Imports System.ServiceProcess

Partial Class Service1

    Inherits ServiceBase

    "outros membros ocultados

    Private components As System.ComponentModel.IContainer

    Private _timer As Timer

    Private Sub InitializeComponent()

        components = New System.ComponentModel.Container()

        Me.ServiceName = "PeopleServices.Service1"

        Me._timer = New Timers.Timer(10000)

        AddHandler _timer.Elapsed, AddressOf Me.OnTimedEvent

    End Sub

End Class

C# - Service1.Designer.cs

using System.Timers;

using System.ServiceProcess;

partial class Service1 : ServiceBase

{

    private System.ComponentModel.IContainer components = null;

    private Timer _timer;

    //outros membros ocultados

    private void InitializeComponent()

    {

        components = new System.ComponentModel.Container();

        this.ServiceName = "Service1";

        this._timer = new Timer(10000);

        this._timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

    }

}

VB.NET – Service1.vb

Imports System.IO

Imports System.Timers

Public Class Service1

    Protected Overrides Sub OnStart(ByVal args() As String)

        Me._timer.Start()

    End Sub

    Private Sub OnTimedEvent(ByVal source As Object, ByVal e As ElapsedEventArgs)

        Using sw As New StreamWriter(File.Open("C:\Logs.txt", FileMode.Append))

            sw.WriteLine(DateTime.Now.ToString("HH:mm:ss"))

        End Using

    End Sub

    Protected Overrides Sub OnStop()

        Me._timer.Stop()

    End Sub

End Class

C# - Service1.cs

using System;

using System.IO;

using System.Timers;

public partial class Service1

{

    //outros membros ocultados

    protected override void OnStart(string[] args)

    {

        this._timer.Start();

    }

    void OnTimedEvent(object sender, ElapsedEventArgs e)

    {

        using (StreamWriter sw = new StreamWriter(File.Open("C:\\Logs.txt", FileMode.Append)))

        {

            sw.WriteLine(DateTime.Now.ToString("HH:mm:ss"));

        }

    }

    protected override void OnStop()

    {

        this._timer.Stop();

    }

}

Quando o método Start do objeto Timer é chamado, ele começa a ser executado e, quando o valor especificado no construtor desta classe, que é o intervalo, for atingido, o evento Elapsed será disparado e, no exemplo acima, escreve uma linha no arquivo especificado, com a hora atual. Isso acontecerá até que o método Stop seja invocado.

Instalando um serviço do Windows

Assim como o .NET Framework trouxe classes para facilitar no desenvolvimento de serviços do Windows, ele também dispõe classes que encapsulam o processo de instalação deste serviço. Ainda dentro do namespace System.ServiceProcess existem duas classes para isso: ServiceInstaller e ServiceProcessInstaller.

A primeira delas, ServiceInstaller, é responsável por instalar um serviço que implementa a classe ServiceBase e é chamada pelo utilitário de instalação quando o serviço está sendo instalado. Essa classe herda indiretamente da classe Installer, que vimos extensivamente a sua estrutura no Capítulo 3 – Utilização de Assemblies.

Para cada serviço a ser instalado, uma instância desta classe é necessária para efetuar as configurações relacionada a cada um deles. A tabela abaixo sumariza as propriedades mais importantes, descrevendo cada uma delas:

Propriedade

Descrição

Description

Recebe uma string contendo a descrição para o serviço. Trata-se de uma descrição para que o usuário que irá operar o serviço saiba para qual finalidade o mesmo se destina.

Essa mensagem também exibida na console de gerenciamento fornecida pelo Windows e também através do utilitário de linha de comando, chamado Sc.exe.

DisplayName

Através de uma string, define um nome amigável que identifica o nome do serviço para o usuário.

ServiceName

Indica o nome uso pelo sistema para identificar o serviço. O valor desta propriedade deve obrigatoriamente ser identica a propriedade ServiceName da classe ServiceBase do serviço que você quer instalar.

StartType

Essa propriedade indica como o serviço será iniciado. Ela recebe uma das opções fornecidas pelo enumerador ServiceStartMode, que pode ser uma das três opções descritas abaixo:

    Propriedade

Descrição

Account

Indica qual o tipo de conta em que o serviço irá rodar. Essa propriedade é definida através do enumerador ServiceAccount, que possui quatro opções, descritas abaixo:

    Imagem 12.2 – Serviço da People já devidamente instalado.

    Interagindo com um serviço do Windows

    Depois de devidamente instalado, é necessário termos o controle sobre ele, ou seja, precisamos ter acesso as operações básicas de todo serviço Windows, que é iniciar, parar, pausar, recuperar informações, etc..

    Para isso, temos três formas. A primeira delas, é utilizando a console que o Windows fornece que está em Painel de Controle, Ferramentas Administrativas e, sem seguida, clique em Serviços; outra possibilidade é através de um utilitário de linha de comando chamado Sc.exe; finalmente, temos a possibilidade de controlar um serviço do Windows a partir de uma aplicação .NET e, para isso, temos uma classe chamada ServiceController. Essa classe representa um determinado serviço do Windows e fornece todas as funcionalidades necessárias para que possamos manipular o serviço que ela representa.

    Quando utilizamos a console fornecida pelo próprio sistema operacional, temos a possibilidade de localizar o serviço e, quando clicamos com o botão direito do mouse em cima do mesmo, um menu de contexto é exibido com as operações que podemos executar no serviço, como por exemplo, o método Start, Stop e Pause. A imagem abaixo ilustra esse menu:

    Imagem 12.3 – Console fornecida pelo Windows para gerenciamento do serviço.

    Além dela, temos também o utilitário de linha de comando, chamado Sc.exe. O utilitário serve para quando queremos automatizar a manipulação do serviço, ou seja, a partir de uma instalação de um software, necessitamos parar um determinado serviço e, quando a instalação for concluída, o serviço é reinicializado. A imagem abaixo exibe como executar esse utilitário e passar os comandos para que ele possa trabalhar:

    Imagem 12.4 – Utilitário de linha de comando para a manipulação de serviços do Windows.

    Finalmente, temos uma solução .NET para acessar um determinado serviço do Windows. Trata-se de uma classe chamada ServiceController. Essa classe fornece o acesso a um serviço do Windows, possibilitando operá-lo e extrair informações sobre ele.

    O interessante é que, como os dois primeiros casos, não está limitado a acessar serviços somente feitos em .NET, mas podendo acessar todo e qualquer serviço Windows. Além disso, ele fornece uma funcionalidade interessante que não temos na console do Windows: é possível criar uma aplicação .NET que envie comandos customizados para o serviço através desta classe. Essa classe também é fornecida como um controle, contida na aba componentes do Visual Studio .NET, como é mostrado na imagem abaixo:

    Imagem 12.5 – Controle ServiceController na ToolBox do Visual Studio .NET.

    Através da tabela abaixo, analisaremos os membros mais importantes que são fornecidos pela classe ServiceController:

    Membro

    Descrição

    CanPauseAndContinue

    Propriedade de somente leitura que retorna um valor booleano indicando se o serviço referenciado permite ou não ser pausado e, em seguida, continuado.

    CanShutdown

    Propriedade de somente leitura que retorna um valor booleano indicando se o serviço referenciado permite ou não ser notificado quando o sistema operacional estiver em processo de shutting down.

    CanStop

    Propriedade de somente leitura que retorna um valor booleano indicando se o serviço referenciado permite ou ser parado depois de inicializado.

    DependentServices

    Recupera um array de elementos do tipo ServiceController, onde cada elemento representa um serviço que depende do serviço referenciado.

    DisplayName

    Retorna uma string contendo o nome amigável do serviço referenciado.

    MachineName

    Propriedade que podemos definir ou ler o nome da máquina em que o serviço reside. O padrão é o computador local, também indicado como “.”.

    ServiceName

    Propriedade que podemos definir ou ler o serviço que a instância do classe ServiceController referencia.

    ServicesDependendOn

    Recupera um array de elementos do tipo ServiceController, onde cada um dos elementos representa um serviço que o serviço referenciado depende.

    ServiceType

    Define um dos valores estipulados pelo enumerador ServiceType, que são descritos abaixo:

      Imagem 12.6 – Anexando o debugger ao serviço Windows.

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.