DOWNLOAD O PROJETO: GOOGLE CODE.com
Hoje vou ensinar vocês a como criar um Windows Service [ Serviço Windows] utilizando C# .NET 4.0 e Thread, pois tive que fazer um e o pessoal que cria ServiçosWindows geralmente não usam Threads e nem configurações no App.Conf e muito menos fazem o serviço ser Auto Instalável, clicando no aplicativo e BUM... tá funcionando para sempre.
Bem vamos começar:
- Abra seu Visual Studio 2010
- Crie um Novo Projeto (File->New->Project ou CRTL+SHIFT+N)
- Selecione a Opção "Windows Service"
- No meu caso o nome da minha aplicação é ServiceWSFeixe e ela vai para a Solução que já tenho criada.
- No seu Solution Explorer deve conter os seguintes arquivos:
- Properties
- References
- Program.cs
- Service1.cs
- A primeira coisa que iremos fazer é organizar os arquivos "CS" pois queremos que ele seja AutoInstaller.
- Renomeie o Service1.cs para Service.cs
- Crie uma nova classe chamada Processo.cs
- Agora vamos programar saída do serviço editando o Service.cs
- Clique com o botão direito no Service.cs e clique em View Code
- E vamo adicionar um método static Main para substituirmos nosso Program.cs
static void Main(string[] args) { } - E dentro deste método iremos criar uma sessão Debug/Release para teste de Threads/Serviço já que é meio chato fazer o debug deles na IDE.
- Deixe a IDE no modo RELEASE de compilação pois iremos fazer o Instalador primeiro e ele não roda em modo DEBUG.
- Para isso usaremos as tags de compilação
static void Main(string[] args) { #if DEBUG #else #endif } - Dentro de #if DEBUG será chamado o Single Processo para "testar" nosso Thread e no #else estará nosso instalador.
- Vá no #else, e a primeira coisa que iremos adicionar será o código que faz o RUN do serviço, para isso abra o Program.cs e copie o que está dentro do static Main() e cole no else, Depois adicione um if de checagem de Ambiente conforme o código abaixo
static void Main(string[] args) { #if DEBUG #else //Verifica se a chamada do Serviço foi ou não chamado pelo usuário. if (!Environment.UserInteractive) { //Chamada pelo Sistema => Executa o Serviço ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service() }; ServiceBase.Run(ServicesToRun); }else { //Chamada pelo Usuário => Instala ou Desinstala o Serviço }; #endif } - Agora podemos Deletar nosso velho amigo Program.cs
- Agora temos que adicionar as variáveis necessárias para o funcionamento do instalador.
public const string NAME = "ServiceWSFeixe"; public const string DISPLAY_NAME = "Feixe WSFeixe"; static void Main(string[] args) { #if DEBUG #else //Verifica se a chamada do Serviço foi ou não chamado pelo usuário. if (!Environment.UserInteractive) { //Chamada pelo Sistema => Executa o Serviço ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service() }; ServiceBase.Run(ServicesToRun); }else { //Chamada pelo Usuário => Instala ou Desinstala o Serviço } #endif }; - Adicione agora um Novo WindowsService, no projeto clique com o botão direito, Add->New Item (CRTL+SHIFT+A)
- Selecione a opção Windows Service novamente e nomeio como ProjectInstaller.cs
- Clique com o Botão Direito no ProjectInstaller.cs e abra o View Code
- E vamos fazer uma completa mudança nela, deixando exatamente conforme abaixo.>
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.IO; using System.Configuration.Install; namespace ServiceWSFeixe { [RunInstaller(true)] public partial class ProjectInstaller : System.Configuration.Install.Installer { public ProjectInstaller() { ServiceProcessInstaller spi = new ServiceProcessInstaller(); spi.Account = ServiceAccount.LocalSystem; System.ServiceProcess.ServiceInstaller si = new System.ServiceProcess.ServiceInstaller(); si.ServiceName = Service.NAME; si.DisplayName = Service.DISPLAY_NAME; si.Description = Service.DISPLAY_NAME; si.StartType = ServiceStartMode.Automatic; Installers.Add(spi); Installers.Add(si); } public static void Install() { string[] s = { Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\" + Service.NAME + ".exe" }; ManagedInstallerClass.InstallHelper(s); ServiceController sc = new ServiceController(Service.NAME); sc.Start(); } public static void Uninstall() { string[] s = { "/u", Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\" + Service.NAME + ".exe" }; ManagedInstallerClass.InstallHelper(s); } } }; - Agora vamos adicionar o código final do #else que nos dará a opção de instalar e desinstalar o serviço através de um MessageBox.Show(). Abra o Service.cs e deixe exatamente como o arquivo abaixo.
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.Windows.Forms; namespace ServiceWSFeixe { public partial class Service : ServiceBase { public const string NAME = "ServiceWSFeixe"; public const string DISPLAY_NAME = "Feixe WSFeixe"; public Service() { ConsoleTraceListener listener = new ConsoleTraceListener(); Trace.Listeners.Add(listener); InitializeComponent(); } static void Main(string[] args) { #if DEBUG try { Trace.WriteLine("Iniciando DEBUG Processo"); Processo.doWork(); } catch(Exception e) { Trace.WriteLine("Erro ao Executar Processo"+e.Message+"\n"+e.StackTrace); } #else //Verifica se a chamada do Serviço foi ou não chamado pelo usuário. if (!Environment.UserInteractive) { //Chamada pelo Sistema => Executa o Serviço ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service() }; ServiceBase.Run(ServicesToRun); } else { //Chamada pelo Usuário => Instala ou Desinstala o Serviço ServiceController sc = new ServiceController(NAME); if (!ServiceExists()) { if (DialogResult.OK == MessageBox.Show("Deseja instalar o serviço " + DISPLAY_NAME + "?", DISPLAY_NAME, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2)) { try { Trace.WriteLine("Instalando o serviço \"" + DISPLAY_NAME + "\"..."); ProjectInstaller.Install(); } catch (Exception ex) { Trace.TraceError(ex.Message); } } } else { if (DialogResult.OK == MessageBox.Show("Deseja desinstalar o serviço " + DISPLAY_NAME + "?", DISPLAY_NAME, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2)) { try { Trace.WriteLine("Desinstalando o serviço \"" + DISPLAY_NAME + "\"..."); ProjectInstaller.Uninstall(); } catch (Exception ex) { Trace.TraceError(ex.Message); } } } } #endif } protected override void OnStart(string[] args) { Processo.Start(); } protected override void OnStop() { Processo.Stop(); } private static bool ServiceExists() { foreach (ServiceController sc in ServiceController.GetServices()) if (sc.ServiceName == NAME) return true; return false; } } } - Como podemos notar eu estou usando Trace, porém eu não ensinei a configuração, e como já instalamos e desinstalamos nosso serviço sem fazer nada, então vamos configurar o Trace.log
- Para isso eu tentei criar o app.config na mão de diversas maneiras e não foi, então eu usei o assistente do Windows.
- Clique com o botão direto do mouse no nome do projeto
- Vá em Properties (Alt+Enter)
- Agora vá na aba Settings
- Adicione um valor como aplicação e com um nome qualquer só para ele criar e linkar o app.config para você
- Agora que o app.config foi criado vamos preencher com o que precisamos para fazer o trace funcionar, com o código abaixo:
c:\log\ - Pronto, assim estaremos prontos para fazer os métodos de Thread na classe Processo.cs que criamos anteriormente.
- Primeiro irei tranformar nossa classe em uma classe 100% estática (static) pois não precisaremos de instanância para chamar no onStart() e no onStop()
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Threading; namespace ServiceWSFeixe { public static class Processo { static Processo() { } //Inicia a Thread e habilita o serviço public static void Start() { } //Para a Thread em ação e desabilita o serviço public static void Stop() { } } } - Agora que nossa classe funciona, vamos colocar as Threads para logar no nosso trace.log e pronto... estamos com um serviço Online agora é só adaptar para sua necessidade
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Threading; namespace ServiceWSFeixe { public static class Processo { static Processo() { } //Inicia a Thread e habilita o serviço public static void Start() { Trace.WriteLine("Processo Start()"); Thread thread = new Thread(new ThreadStart(doWork)); thread.Start(); Thread thread2 = new Thread(new ThreadStart(doWorks)); thread2.Start(); } //Para a Thread em ação e desabilita o serviço public static void Stop() { } //Executa o processo SEM Thread para passar no modo debug public static void doWork() { for (int x = 0; x <= 10000; x++) { Trace.WriteLine("x = " + x); } } //Executa o processo SEM Thread para passar no modo debug public static void doWorks() { for (int x = 10000; x > 1; x--) { Trace.WriteLine("Y = " + x); } } } }
Parabéns cara! Ótimo artigo! Abraços
ResponderExcluirVlw Roger, Abraços!!
ResponderExcluirAmigo, achei muito interessante!
ResponderExcluirMas não consegui fazer funcionar, teria o fonte pra analizarmos?
Max System, vou preparar um repositório e colocar o link disponibilizando o Código essa semana, hoje estou meio enrolado então não sei se vai ser possível.
ResponderExcluirParabéns pelo post.
ResponderExcluirConsigo fazer executar, aparecendo o alert de instalação, mas não aparece mais nada, o serviço não aparece no services.msc.
Obrigado!!
Charles,
Excluirno CS da instalação tem a varável estática NAME = "" ela tem que ser o nome do projeto identico ao que será gerado o executavel.
Por exemplo projeto "TesteServico" vai gerar um "TesteServico.exe" então o nome á tem que ser "TesteServico".
Aconteceu o seguinte erro ao executar:
ResponderExcluir"O sistema de configuração falhou ao inicializar"
Na linha Trace.WriteLine("Iniciando DEBUG Processo"); da classe Service.cs
Muito bom, me ajudou muito.
ResponderExcluir