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:
- Creating the project, initial service and installation
- Adding additional services to the application
- 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";
}
protected override void OnStart(string[] args)
{
base.OnStart( args );
}
protected override void OnStop()
{
base.OnStop();
}
protected override int Execute() {
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)
{
ServiceController controlAdmin =
new ServiceController("SpadesAdminSvc", ".");
if( controlAdmin.Status !=
System.ServiceProcess.ServiceControllerStatus.Running &&
controlAdmin.Status != ServiceControllerStatus.StartPending ) {
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)
{
ServiceBase[] servicesToRun;
servicesToRun = new ServiceBase[]{
new SpadesAdminService(),
new SpadesLoginService(),
new SpadesGameService(),
new SpadesLobbyService() };
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" };
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