Tuesday, October 30, 2007 10:26 PM

Meu primeiro artigo sobre desenvolvimento .Net, publicado na MSDN Brasil Magazine, ensinava como criar um Windows service em .Net. Na época o assunto era uma grande novidade. Mais de quatro anos , no entanto, até hoje vejo perguntas a este respeito no Fórum da MSDN ou em grupos de discussões. Por isto, decidi escrever uma nova versão, mais moderna, mas que basicamente cobre o mesmo assunto.

A primeira diferença é o formato, como estou publicando no blog, vou picotar o artigo em pequenas cápsulas mais fáceis de digerir. Além disto, vou inverter totalmente a ordem que fiz antes e que vejo em todo artigo sobre Windows Service. Ao invés de começar montando toda a infra-estrutura do serviço, vou começar implementando e testando nossas necessidades de negócio. Afinal de contas esta parte é mais importante. O serviço, na realidade, é apenas um meio para atingirmos nossos objetivos.

Existem dois requisitos que são os favoritos de quem precisa criar um serviço:

  • Monitorar um diretório, identificar a criação/alteração de um arquivo e processá-lo
  • Executar um processo periodicamente

Então vamos começar implementando um monitor de diretórios. Sua função é monitorar um diretório, avaliando os eventos acontecidos e processar arquivos que são criados ou alterados neste diretório. Um monitor como este precisa saber qual diretório deve monitorar, o nome do arquivo, ou uma expressão regular para nomes de arquivos variáveis. Além disto, deve possibilitar iniciar e parar o monitor. Isto é bem simples usando o FileSystemWatcher, mesmo assim vou fazer uma classe utilitária para isolar este contato com uma API de infra-estrutura. A Listagem 1 mostra como fica esta classe.

Listagem 1: Isolando o contato com o file system

    public class FolderWatcher
    {
        private FileSystemWatcher _folderWatcher;
        private bool _isWatching = false;
        private IProcessFile _processFile;


        public bool isWatching
        {
            get { return _isWatching; }
        }

        public void WatchFolder(string _folderPath, string _filter, IProcessFile processFile)
        {
            _processFile = processFile;
            _folderWatcher = new FileSystemWatcher(_folderPath, _filter);
            _folderWatcher.NotifyFilter = NotifyFilters.FileName;
            _folderWatcher.Created  += OnCreated;
            _folderWatcher.EnableRaisingEvents = true;

            _isWatching = true;
        }

        public void StopWatching()
        {
            _folderWatcher = null;
            _isWatching = false;
        }
 
        private void OnCreated(object sender, FileSystemEventArgs e)
        {
            _processFile.Process(e.FullPath);
        }

A classe é simples e direta, oferece apenas dois métodos: WatchFolder() e StopWatching(). O primeiro recebe como parâmetros o endereço do diretório a monitorar e o filtro para o nome do arquivo, que pode ser uma expressão regular. Por último ele recebe uma interface, que será responsável pro processar o arquivo. Esta é uma forma de abstrair desta classe o processamento do arquivo.

Meu problema agora é testar esta classe. Existem duas opções: Criar mockings para simular o comportamento da classe FileSystemWatcher ou simular a criação do arquivo em um diretório. Apesar de a segunda opção ser menos ortodoxa do ponto de vista de testes unitários, pois não isola o teste, vou optar por ela. O motivo é simples, a classe FolderWatcher praticamente não tem comportamento próprio, desta forma não vejo grandes ganhos em testá-la isoladamente.

Listagem 2: Testando o FolderWatcher

    [TestFixture]
    public class FolderWatcherTest
    {
        private string _testFolderPath;
        private string _randomFileName;
        private string _filePath;
        
        [SetUp]
        public void Setup()
        {
            _testFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            _randomFileName = Path.GetRandomFileName();
            _filePath = string.Format(@"{0}\{1}", _testFolderPath, _randomFileName);
        }

        [Test]
        public void Inicia_E_CriaArquivo()
        {
            MockRepository mockery = new MockRepository();
            string filter = _randomFileName;

            IProcessFile mockedProcessFile = mockery.CreateMock<IProcessFile>();
            Expect.Call(delegate { mockedProcessFile.Process(_filePath); }).Repeat.Once();

            mockery.ReplayAll();
            FolderWatcher folderWatcher = new FolderWatcher();
            folderWatcher.WatchFolder(_testFolderPath, filter, mockedProcessFile);
            CreateDummyFile();
            Wait(1000);

            mockery.VerifyAll();
        }

        [TearDown]
        public void TearDown()
        {
            ClearFile();
        }

        private void CreateDummyFile()
        {
            TextWriter writer = new StreamWriter(_filePath);
            writer.Write("qualquer texto");
            writer.Flush();
            writer.Close();
        }

        private void ClearFile()
        {
            File.Delete(_filePath);
        }

        private void Wait(int timeout)
        {
            System.Threading.Thread.Sleep(timeout);
        }
    }

A Listagem 2 mostra um teste que inicia o meu FolderWatcher e testa o monitor, criando um arquivo no diretório monitorado. Esto utilizando a ferramenta de mock, Rhino Mocks, para abstrair a classe que na vida real vai processar o arquivo recém criado. Defino que o método Process() será chamado somente uma vez, com o caminho para o arquivo como argumento.

Tive que fazer um pequeno hack, que é o método Wait. Isto não é muito bom, pois cria mais uma variável de falha no teste, mas necessário, porque o FileSystemWatcher não reage imediatamente a criação do arquivo. Preciso esperar um segundo para a coisa toda funcionar. Provavelmente existe uma forma melhor de fazer isto, sugestões são bem-vindas.

Este é o teste mais importante, mas para garantir a qualidade da classe é necessário testar outros cenários, como enviar um endereço de diretório inválido, ou criar o arquivo com o FolderWatcher parado. Quanto mais cenários testados, melhor.

Próximo capítulo vamos implementar outra função básica necessária que é a execução de processo periódico que irá rodar em background, sem a interação de um usuário para iniciá-lo.

Antes que eu me esqueça, vou liberar o código também aos poucos, evoluindo a cada capítulo. Pode pegar sua cópia aqui.

< Exemplos >

Comments

At 11/7/2007 11:41 AM, Alexsandro dos Santos Pimenta said:

# re: Criando um Windows Service em C# - Parte 1 / 4

Olá,

Como eu posso desenvolver uma interface para meu window service? Eu desenvolvi um window service, e mais uma aplicação window form que fica na área de notify (aquele espaço que tem a o relógio no windows). Criei umas propriedades publicas no window service, que indicam o que ele está fazendo, mas não consigo capturar a instância do objeto do meu window service para ver estas propriedades na aplicação window form. Eu preciso desenvolver algo como os programas de antivírus que um serviço fica rodando e tem uma interface que mostra o que o serviço está fazendo. Qual a melhor forma?

Alex
At 11/8/2007 12:32 PM, Eduardo Miranda said:

# re: Criando um Windows Service em C# - Parte 1 / 4

Alex,

Não conheço bem os requisitos da sua solução. Mas me parece que se você já tem uma aplicação executando o tempo todo, minimizada, o Windows service é desnecessário. A própria aplicação é capaz de fazer tudo o que o serviço faria.

Aliás, esta é uma ótima idéia para um novo requisito para a minha solução: "e se o seu cliente quiser um 'toaster' informando-o das execuções das tarefas em background".
At 12/18/2007 10:19 AM, said:

# Final de Semana

Final de Semana
At 8/28/2008 3:04 PM, lolzao pirokao said:

# re: Criando um Windows Service em C# - Parte 1 / 4

mt bom
At 6/4/2009 4:12 PM, Flávio said:

# re: Criando um Windows Service em C# - Parte 1 / 4

Totalmente confuso este seu arquivo !!!
At 2/17/2010 8:48 PM, Vinicius said:

# re: Criando um Windows Service em C# - Parte 1 / 4

Muito confuso... Acho que tinha que ao menos informar que tipo de projeto teria que ser criado... É um projeto windows form web console ou o que? Ja começa no meio...
At 2/22/2010 3:33 PM, Eduardo Miranda said:

# re: Criando um Windows Service em C# - Parte 1 / 4

O objetivo deste artigo não era fazer mais um passo a passo de como desenvolver um serviço em .Net. Na internet é possível encontrar diversos artigos com este objetivo.
Post Comment
Title *
Name *
Email (never displayed)
Website
Comment * (Allowed tags: blockquote, a, strong, em, p, u, strike, super, sub, code)  
Please add 8 and 5 and type the answer here: