Click here to Skip to main content
Click here to Skip to main content

Creating a C# Service Step-by-Step: Lesson I

, 9 Apr 2003
Rate this:
Please Sign up or sign in to vote.
A multi-article contribution describing in step-by-step detail on creating your own service with integrated support for setup and custom event logs.

Introduction

Recently I was trying to write a C# service using the .NET framework and ran into a lot of issues that were not covered in the documentation or if they were, the solution wasn't easily inferred. So I decided to write a series of articles describing in step-by-step detail, how I went about solving the problems I encountered.

This is a multi-article contribution which is broken down as follows:

  1. Creating the project, initial service and installation
  2. Adding additional services to the application
  3. Adding support for custom event logging

Using the code

Lets start off by creating a new C# project that is a console application. Visual Studio .NET does give you a wizard to create a Windows Service using C#, but I'll take it from scratch so that those who don't have VS.NET can still follow along. Figure 1 shows the initial project creation screen.

Sample screenshot

Figure 1

Now that we have a simple console application created, we need to change some of the defaults that are created by the wizard. Lets open up the Class1.cs file. This file contains the entry point for the application, but we're going to rename the class Class to better reflect it's role in the application. Right click the Class1.cs node in the Solution Explorer and choose Rename. Rename the file Application.cs. Then right click and choose View Code so that we can edit the code directly. We're going to rename the class Class to Application.

We are also going to change the namespace to reflect where the class exists within our company's namespace. I have a domain that I use for my sample code, called CodeBlooded and I'm going to use this as my company name. You are free to use any name that you want. Since this service is part of a fictional Spades game (that I may end up fully developing over time), we are going to name our namespace CodeBlooded.Spades.Services. Edit the new Application.cs file and change the namespace from the default of SpadesServer to CodeBlooded.Spades.Services.

To keep us from having to make these changes for every class we add to the project, open up the project properties dialog by right clicking the SpadesServer project in our solution and choose Properties. In the project properties dialog, go to the Common Properties | General tab and change the Application | Default Namespace field to CodeBlooded.Spades.Services. From now on, any class we create will automatically be assigned to this namespace. Figure 2 shows you what this tab should look like.

Sample screenshot

Figure 2

While we are in the project settings dialog, go to the Configuration Properties | Build tab and change the Outputs | XML Documentation File settings to SpadesServer.xml. This is an XML file that the compiler will create for us to help us build our documentation for the project. Look in the .NET framework SDK documentation on what documentation tags are supported. Figure 3 shows what this dialog should look like.

Sample screenshot

Figure 3

Now that we've taking care of some configuration details in time to get started on actually creating some code. The .NET framework has a base class ServiceBase that provides the interface to implement a service. This class must be derived from, to add our code that actually has the logic for the services we want to create. We must override the default methods of the OnStart and OnStop methods. These methods are called by the Service Control Manager to actually control the service.

The problem is that the OnStart and OnStop methods must return control back to the Service Control Manager within 1 minute, for the Service Control Manager to recognize that the service is running or has stopped. So how do we actually get any work done, if we must return within one minute. We must create a background thread that will perform all the work that our service is going to do.

To keep from having to write the same code for each of the classes that we will create during this tutorial, I'm going to create a base class that is responsible for creating the worker thread as well as communicating with the SCM. Create a new class that we'll call SpadesServiceBase. The following code is what the class should look like.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;

namespace CodeBlooded.Spades.Services
{
    public class SpadesServiceBase : System.ServiceProcess.ServiceBase
    {
        public SpadesServiceBase()
        {
            // TODO: Add any initialization code here
        }

        /// <SUMMARY>
        /// Set things in motion so your service can do its work.
        /// </SUMMARY>
        protected override void OnStart(string[] args)
        {
            // TODO: Add code here to start your service.
        }
 
        /// <SUMMARY>
        /// Stop this service.
        /// </SUMMARY>
        protected override void OnStop()
        {
            // TODO: Add code here to perform any tear-down
            // necessary to stop your service.
        }
    }
}

We will eventually create 3 services to control the state of the game, with an administration service that will control and/or manage the other 3 services. To handle this, the administration service will derive from the SpadesServiceBase class but we'll create another class that derives from SpadesServiceBase class that the 3 child services will derive from. This helps us isolate the shared code better, by having the lowest level base class contain only the code that is applicable to all services, and the child services base class will contain the common code that is shared across all child services.

So lets create another class called SpadesChildServiceBase and have it derive from SpadesServiceBase. Create the file any way you want, but the code should end up looking like the following:

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;

namespace CodeBlooded.Spades.Services
{
    public class SpadesChildServiceBase : SpadesServiceBase
    {
        public SpadesChildServiceBase()
        {
            // TODO: Add any initialization code here
        }

        /// <SUMMARY>
        /// Set things in motion so your service can do its work.
        /// </SUMMARY>
        protected override void OnStart(string[] args)
        {
            // TODO: Add code here to start your service.
            base.OnStart( args );
        }
 
        /// <SUMMARY>
        /// Stop this service.
        /// </SUMMARY>
        protected override void OnStop()
        {
            // TODO: Add code here to perform any
            // tear-down necessary to stop your service.
            
            base.OnStop();
        }
    }
}

Now we're going to write the code to create the background thread and the synchronization objects to be used by the SCM to start and stop the background thread. The background thread will periodically wake up and perform some action. If you were going to write a service that was dependant on messages coming from a client, then you use whatever IPC mechanism you wanted to control the communication. For best performance, you should make this code event driven instead of writing a poling mechanism. In future articles I may describe the way to accomplish this by using asynchronous socket calls, but for now I'm going to simply create a poling mechanism to manage the workflow of the services.

To facilitate the base classes communicating with the more specific derived classes, the base class will provide a pure virtual method called Execute that will need to be implemented by the service. The Execute method is called periodically by the base classes as they periodically get signaled to run by our timer. The Execute method should contain the specific logic that you would need to do periodically, to accomplish what each individual service is responsible for.

We need to add a reference to System.Threading to our SpadesServiceBase class and add a member variable of Thread that we'll call m_thread. We will also need a ManualResetEvent from the Threading namespace to be used to communicate our stop request from the SCM. In the OnStart method of the SpadesServiceBase, we'll create an instance of a Thread object as well as the ManualResetEvent. To run a thread, we need to define a thread start method that is the entry point for the thread. In our case we'll call our method ServiceMain and use a ThreadStart class from the framework to define our ServiceMain as a delegate method. Modify your SpadesServiceBase class to look like the following code block.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;

namespace CodeBlooded.Spades.Services
{
    public class SpadesServiceBase : System.ServiceProcess.ServiceBase
    {
        public SpadesServiceBase() {
            // create a new timespan object
            // with a default of 10 seconds delay.
            m_delay = new TimeSpan(0, 0, 0, 10, 0 );
        }

        /// <SUMMARY>
        /// Set things in motion so your service can do its work.
        /// </SUMMARY>
        protected override void OnStart(string[] args) {
            // create our threadstart object to wrap our delegate method
            ThreadStart ts = new ThreadStart( this.ServiceMain );

            // create the manual reset event and
            // set it to an initial state of unsignaled
            m_shutdownEvent = new ManualResetEvent(false);

            // create the worker thread
            m_thread = new Thread( ts );

            // go ahead and start the worker thread
            m_thread.Start();

            // call the base class so it has a chance
            // to perform any work it needs to
            base.OnStart( args );
        }
 
        /// <SUMMARY>
        /// Stop this service.
        /// </SUMMARY>
        protected override void OnStop() {
            // signal the event to shutdown
            m_shutdownEvent.Set();

            // wait for the thread to stop giving it 10 seconds
            m_thread.Join(10000);

            // call the base class 
            base.OnStop();
        }

        /// <SUMMARY>
        /// 
        /// </SUMMARY>
        protected void ServiceMain() {
            bool        bSignaled    = false;
            int            nReturnCode    = 0;
            
            while( true ) {
                // wait for the event to be signaled
                // or for the configured delay
                bSignaled = m_shutdownEvent.WaitOne( m_delay, true );

                // if we were signaled to shutdow, exit the loop
                if( bSignaled == true )
                    break;

                // let's do some work
                nReturnCode = Execute();
            }
        }

        /// <SUMMARY>
        /// 
        /// </SUMMARY>
        /// <RETURNS></RETURNS>
        protected virtual int Execute() {
            return -1;
        }

        protected Thread            m_thread;
        protected ManualResetEvent    m_shutdownEvent;
        protected TimeSpan            m_delay;
    }
}

Now that we have these details taken care of, lets go ahead and create the administrative service. Right now the service won't be responsible for a whole lot, but over the next few lessons we'll add more functionality to it. For now create a new class SpadesAdminService and derive it from SpadesServiceBase. The code below is the boilerplate code for the administrative service.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;

namespace CodeBlooded.Spades.Services
{
    public class SpadesAdminService : SpadesServiceBase
    {
        public SpadesAdminService()
        {
            this.ServiceName = "SpadesAdminSvc";
        }

        /// <SUMMARY>
        /// Set things in motion so your service can do its work.
        /// </SUMMARY>
        protected override void OnStart(string[] args)
        {
            base.OnStart( args );
        }
 
        /// <SUMMARY>
        /// Stop this service.
        /// </SUMMARY>
        protected override void OnStop()
        {
            base.OnStop();
        }

        /// <SUMMARY>
        /// 
        /// </SUMMARY>
        /// <RETURNS></RETURNS>
        protected override int Execute() {
            
            // for right now we'll just log a message in the
            // Application message log to let us know that
            // our service is working
            System.Diagnostics.EventLog.WriteEntry("SpadesAdminSvc", 
                                          ServiceName + "::Execute()");

            return 0;
        }
    }
}

Ok now that we have a service written, we need to do some more plumbing work to communicate to Windows that we have services in this executable. To do this is straightforward and all we need to do is create a collection of ServiceBase objects and simply call the Run method of the base class ServiceBase. Add the following code to the Main method in the Application object. The following code fragment shows all that is necessary to start our services.

static void Main(string[] args)
{
    // we'll go ahead and create an array so that we
    // can add the different services that
    // we'll create over time.
    ServiceBase[]    servicesToRun;

    // to create a new instance of a new service,
    // just add it to the list of services 
    // specified in the ServiceBase array constructor.
    servicesToRun = new ServiceBase[] { new SpadesAdminService() };

    // now run all the service that we have created.
    // This doesn't actually cause the services
    // to run but it registers the services with the
    // Service Control Manager so that it can
    // when you start the service the SCM will call
    // the OnStart method of the service.
    ServiceBase.Run( servicesToRun );
}

We're getting really close to wrapping this lesson up and all we need to do to be able to run the service, is add our Installers. The installers can either be called through a setup program that I may document in some future article or they can be called by the InstallUtil program that comes with the framework SDK.

Add a new installer to the project and we'll call it SpadesInstaller. After you run the Add Class wizard, we'll add some code to the class constructor. The installers use attributes that are applied to the class, where the installer will create an instance of each class that has the RunInstaller attribute applied to the class.

All we'll need is an instance of the ServiceProcessInstaller to control how the service process is started and then for each service that we will be installing, we'll create a ServiceInstaller object. After setting some properties on these objects, we'll then add it to the Installers collection. The following code shows all that we need to install our service.

using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;
namespace CodeBlooded.Spades.Services
{
    /// <SUMMARY>
    /// Summary description for SpadesInstaller.
    /// </SUMMARY>
    [RunInstaller(true)]
    public class SpadesInstaller : System.Configuration.Install.Installer
    {
        public SpadesInstaller()
        {
            ServiceProcessInstaller process = new ServiceProcessInstaller();

            process.Account = ServiceAccount.LocalSystem;

            ServiceInstaller serviceAdmin = new ServiceInstaller();

            serviceAdmin.StartType        = ServiceStartMode.Manual;
            serviceAdmin.ServiceName    = "SpadesAdminSvc";
            serviceAdmin.DisplayName    = "Spades Administration Service";
            
            // Microsoft didn't add the ability to add a
            // description for the services we are going to install
            // To work around this we'll have to add the
            // information directly to the registry but I'll leave
            // this exercise for later.


            // now just add the installers that we created to our
            // parents container, the documentation
            // states that there is not any order that you need to
            // worry about here but I'll still
            // go ahead and add them in the order that makes sense.
            Installers.Add( process );
            Installers.Add( serviceAdmin );
        }
    }
}

After compiling to test our code, all we need to do is start a command prompt and move to our output directory. For now we'll just use the bin\Debug subdirectory of our project and run the following command to have our service installed.

installutil SpadesServer.exe

If you want to uninstall the service run the following command.

installutil SpadesServer.exe -U

Once this is complete, we should be able to start and stop the service from the Service Control Manager, to our hearts content.

Over the next few articles, we'll add more functionality to the framework that we've created here. We'll add some child services that when they start will start the admin service if it's not already started. The admin service will shutdown the child services if the admin service is stopped. This is similar to the way IISAdmin controls the different web services. In addition, we'll add a custom message log as well as a message log installer.

History

  • 1.0 - 9 Apr 2003
    • First release version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Terry Denham
Architect Match.com
United States United States
No Biography provided

Comments and Discussions

 
QuestionIf you get "Remove InstallState file because there are no installers" message when installing Pinmemberahmd027-Mar-13 17:37 
GeneralC# Services onstart,onstop Pinmemberswathi.N6-Jan-11 23:37 
GeneralMy vote of 4 PinmemberKanasz Robert13-Nov-10 6:07 
GeneralArguments past to Main() [modified] PinmemberJordanwb22-Nov-09 10:12 
GeneralMobile Pinmembermheese7-Oct-09 6:50 
Generalwhy not use service as start in the wizard PinmemberDonsw21-Feb-09 10:57 
GeneralRe: why not use service as start in the wizard Pinmemberwmjordan13-Apr-11 17:27 
General[Message Removed] Pinmemberimmetoz6-Oct-08 7:53 
Generalcan't debug Pinmembernareshpersi24-Apr-08 0:58 
Questionservice can't start automatically Pinmemberalphacode2-Jul-07 20:47 
QuestionOnStop not working PinmemberNRworld28-Mar-07 5:04 
AnswerRe: OnStop not working Pinmemberke4vtw25-Apr-07 7:08 
Generaldoesn't work with VC++ 2005 PinmemberStober13-Jul-06 5:56 
GeneralRe: doesn't work with VC++ 2005 PinmemberDavid Roh4-Feb-07 11:23 
GeneralConfusing comment Pinmemberhobyrne16-Jan-06 3:49 
GeneralRe: Confusing comment Pinmemberbrucieg26-Apr-06 5:45 
Generalservice with ActiveX Pinmemberb_asi30-Aug-05 5:58 
AnswerRe: service with ActiveX PinmemberLensouille1-Sep-05 2:32 
QuestionCan I include windowsform component in services??? Pinmemberpraetorion19-Jul-05 23:39 
AnswerRe: Can I include windowsform component in services??? PinmemberMarcZ21-Feb-07 3:49 
QuestionWhy no System.ServiceProcess? Pinmemberreggie_sa25-May-05 17:14 
AnswerRe: Why no System.ServiceProcess? Pinmemberreggie_sa25-May-05 17:36 
GeneralRe: Why no System.ServiceProcess? Pinmembershapeshifter99910-Jan-06 4:22 
GeneralGetting ERROR on Execute() PinmemberRamstar21-Aug-04 14:26 
GeneralRe: Getting ERROR on Execute() Pinmemberan_phu23-Mar-05 13:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140821.2 | Last Updated 10 Apr 2003
Article Copyright 2003 by Terry Denham
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid