Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating a C# Service Step-by-Step Lesson II

0.00/5 (No votes)
9 Apr 2003 5  
A multi-article contribution describing in step-by-step detail on creating your own service with integrated support for setup and custom event logs. This lesson we'll add multiple child services as well as updating the installer to install these services as well.

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 it 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

Starting with the code we developed during the last lesson, we are going to create some child services that are responsible for different aspects of the Spades game server. During this lesson, we are going to create a Login service, Lobby service and Game service. All these services will derive from the SpadesChildServiceBase class that we developed in the last lesson. We will also need to create an instance of these classes in the Application object, as well as create different ServiceInstaller classes to install the 3 different child services.

So without further delay, lets get started creating our first child process. All the child processes will be similar in structure, so I won't list the source code for each one, but the basic difference is just the class name. So let's go ahead and create a new class and call it SpadesLoginService and derive it from SpadesChildServiceBase. Your code should look like this when you are done.

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

namespace CodeBlooded.Spades.Services
{
    public class SpadesLoginService : SpadesChildServiceBase
    {
        public SpadesLoginService()
        {
            this.ServiceName = "SpadesLoginSvc";
        }

        /// <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(ServiceName, 
                                        ServiceName + "::Execute()");

            return 0;
        }
    }
}

Since we want our child service to start the admin service if it's not already started, we need to add this code to the SpadesChildServiceBase class. We will also need to add some shutdown code to the SpadesAdminService class to shutdown all the child services when we stop the admin service.

Open up the SpadesChildServiceBase.cs file and in the OnStart method, we will use a ServiceController to gain access to a specific service on a specific computer and control the service via the Start or Stop methods. Just add the following code to your OnStart method of the SpadesChildServiceBase class.

protected override void OnStart(string[] args)
{
    // gain access to the SpadesAdminSvc on the local computer.

    ServiceController controlAdmin = 
       new ServiceController("SpadesAdminSvc", ".");

    if( controlAdmin.Status != 
        System.ServiceProcess.ServiceControllerStatus.Running &&
        controlAdmin.Status != ServiceControllerStatus.StartPending ) {
        
        // start the admin service

        controlAdmin.Start();
    }

    base.OnStart( args );
}

We need to create an instance of the SpadesLoginService class in the Application class, in the Main method. The code looks like:

servicesToRun = new ServiceBase[]{    new SpadesAdminService(),
                new SpadesLoginService() };

All we have left to do at this point is, create another ServiceInstaller class in our SpadesInstaller class. The code looks like the following and is just like the code we wrote in the previous article. The only thing different in the child services is that, we configure the installer to add the ServiceDependOn keys to the registry. Since this key is a MULTI_SZ type in the registry, it can hold multiple strings, so the way we do it in .NET is to create a string array and add the different strings to the array. Since we really only have one service that the child services depend on, we only need the one entry of SpadesAdminSvc.

        public SpadesInstaller()
        {

            // ...

            
            ServiceInstaller serviceLogin = new ServiceInstaller();

            serviceLogin.StartType    = ServiceStartMode.Manual;
            serviceLogin.ServiceName    = "SpadesLoginSvc";
            serviceLogin.DisplayName    = "Spades Login Service";
            serviceLogin.ServicesDependedOn  
                  = new string[] { "SpadesAdminSvc" };
            
            // ...

            Installers.Add( serviceLogin );
        }

Go ahead and create 2 more classes, one SpadesGameService and SpadesLobbyService both of which derive from SpadesChildServiceBase. Be sure to change the ServiceName property in your constructor. In our case, they should be SpadesGameSvc and SpadesLobbySvc respectively.

You will also need to create an instance of each in the Application::Main method and add them to the servicesToRun variable.

You will also need to create two ServiceInstaller class instances in the SpadesInstaller class just like we did above. When you are done, the Main method and the SpadesInstaller files should look like this:

        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(), 
                            new SpadesLoginService(),
                            new SpadesGameService(),
                            new SpadesLobbyService() };

            // 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 );
        }

And the SpadesInstaller class.

        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";
            
            ServiceInstaller serviceLogin = new ServiceInstaller();

            serviceLogin.StartType    = ServiceStartMode.Manual;
            serviceLogin.ServiceName    = "SpadesLoginSvc";
            serviceLogin.DisplayName    = "Spades Login Service";
            serviceLogin.ServicesDependedOn = 
                        new string[] { "SpadesAdminSvc" };

            ServiceInstaller serviceGame = new ServiceInstaller();

            serviceGame.StartType    = ServiceStartMode.Manual;
            serviceGame.ServiceName    = "SpadesGameSvc";
            serviceGame.DisplayName    = "Spades Game Service";
            serviceGame.ServicesDependedOn = 
                        new string[] { "SpadesAdminSvc" };

            ServiceInstaller serviceLobby = new ServiceInstaller();

            serviceLobby.StartType    = ServiceStartMode.Manual;
            serviceLobby.ServiceName    = "SpadesLobbySvc";
            serviceLobby.DisplayName    = "Spades Lobby Service";
            serviceLobby.ServicesDependedOn = 
                        new string[] { "SpadesAdminSvc" };

            // 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 );
            Installers.Add( serviceLogin );
            Installers.Add( serviceGame );
            Installers.Add( serviceLobby );
        }

In my next article, we're going to add support for having our own custom event log so that we can log our messages to it, but to take advantage of it we will also need to make sure it's installed. So we'll have to create an installer for it. I ran into problems doing this and will go into detail on how I had to solve the problem.

History

  • 1.0 - 10 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