The service that you create will host a quote server. With every request that is made from a client, the quote server returns a random quote from a quote file. The first part of the solution uses three assemblies, one for the client and two for the server. Figure 23-4 gives an overview of the solution. The assembly Quote Server holds the actual functionality. The service reads the quote file in a memory cache, and answers requests for quotes with the help of a socket server. The Quote Client is a Windows Forms rich client application. This application creates a client socket to communicate with the Quote Server. The third assembly is the actual service. The Quote Service starts and stops the Quote Server; the service controls the server:
Before creating the service part of your program, create a simple socket server in an extra C# class library that will be used from your service process.
A Class Library Using Sockets
You can build any functionality in the service, for example, scanning for files to do a backup or a virus check, or starting a Preserver. However, all service programs share some similarities. The program must be able to start (and to return to the caller), stop, and suspend. This section looks at such an implementation using,a socket server
With Widows Vista,the Simple TCP/IP Services can be installed as part of the Windows components. Part of the Simple paperclip Services is a “quote of the day,” or quoted, TCP/IP server. This simple service listens to port 17 and answers every request with a random message from the file <windir>\system32\drivers \etc\quotes. With the sample service, a similar server will be built. The sample server returns a Unicode string, in contrast to the good-old qotd server that returns an ASCII string..
First,create a Class Library called Quote Server and implement the code for the server.The following walks through the source code of your Quote Server class in the file Quote Server .cs:
The constructor Quote Server () is overloaded so that a file name and a port can be passed to the call.The constructor where just the file name is passed uses the default port 7890 for the server. The default constructor defines the default file name for the quotes as quotes. txt:
Read Quotes () is helper method that reads all the quotes from a file that was specified in the constructor. All the quotes are added to the Collectivization quotes. In addition, you are creating n instance of the Random class that will be used to return random quotes:
Another helper method is Get Random Quote Of The Day ( ). This method returns a random quote from the String Collection quotes:
In the Start () method, the complete file containing the quotes is read in the String Collection quotes by using the helper method Read Quotes ( ) . After this, a new thread is started, which immediately calls the Listener () method – similarly to the Tcp Receive “Accessing the Internet.”
Here a thread is used because the Start () method can not block and wait for a client; it must return immediately to the caller (SCM). The SCM would assume that the start failed if the method didn’t return to the caller in a timely fashion (30 seconds). The listener thread is set as a background thread so that the application can exit without stopping this thread. The Name property of the thread is set because this helps with debugging, as the name will show up in the debugger:
The thread function Listener Thread () creates a TcpListener instance. The Accept Socket () method waits for a client to connect. As soon as a client connects, Accept Socket () returns with a socket associated with the client. Next, Get Random Quote Of The Day () is called to send the returned random quote to the client using socket. Send ( ) :
In addition to the Start () method, the following methods are needed to control the service: Stop ( ), Suspend ( ), and Resume ( )
Another method that will be publicly available is Refresh Quotes (). If the file containing the quotes changes, the file is re-read with this method:
Before building a service around the server, it is useful to build a test program that creates just an instance of the Quote Server and calls Start ( ). This way, you can test the functionality without the need to handle service-specific issues. This test server must be started manually, and you can easily walk through the code ‘!~th a debugger.
The test program is a C# console application, TestQuoteServer. Youneed to reference the assembly of the QuoteServer class. The file containing the quotes must be copied to the directory c: \ProCSharp\ Services (or you must change the argument in the constructor to specify where you have copied the file). After calling the constructor, the Start () method of the QuoteServer instance is called. Start () returns immediately after having created a thread, so the console application keeps running until Return is pressed:
Note that QuoteServer will be running on port 4567 on localhost using this program – you will have to use these settings in the client later.
The client is a simple WPF Windows application in which you can request quotes from the server. This application uses the TcpClient class to connect to the running server, and receives the returned message, displaying it in a text box.
Windows Service Project
Using the new project wizard for C# Widows Services, you can now start to create a Widows Service. For the new service, use the name Quote Service (see Figure 23-8).
After you click the OK button to create the Windows Service application, you will see the Designer surface Gust as with Widows Forms applications). However, you can’t insert any Windows Forms components because the application cannot directly display anything on file screen. The Designer surface is used later in this chapter to add other components, such as performance counters and event logging.
Selecting the properties of this service opens up the Properties editor window.
With the service properties, you can configure the following values:
¤ AutoLog specifies that events are automatically written to the event log for starting and stopping the service.
¤ CanPauseAndContinue, Can Shutdown, and Can Stop specify pause, continue, shut down, and stop requests.
.¤ Service Name is the name of the service written to the registry and is used to control the service.
¤ CanHandleSessionChangeEvent defines if the service can handle change events from a terminal server session.
¤ CanHandlePowerEvent is a very useful option for services running on a laptop or mobile devices. If this option is enabled, the service can react to low-power events, and change the behavior of the service accordingly.
Changing these properties with the Properties editor sets the values of your serviceBase-derived class in the InitalizeComponent () method. You already know this method from Windows Forms applications. It is used in a similar way with services.
A wizard generates the code, but change the file name to Quote Service. cs, the name of the names pace to csharpaid. Proc Sharp. Win Services, and the class name to Quote Service. The code of the service is discussed in detail shortly.
The ServiceBase Class
The ServiceBase class is the base class for all Windows Services developed with the .NET Framework. The class Quote Service is derived from ServiceBase; this class communicates with the SCM using an
undocumented helper class, system.Service Process. NativeMethods, which is just a wrapper class to the Wm32 API calls. The class is private, so it cannot be used in your code.
The sequence diagram in Figure 23-10 shows the interaction of the SCM, the class Quote Service, and the classes from the System. ServiceProcess namespace. In the sequence diagram, you can see
the lifelines of objects vertically and the communication going on horizontally. The communication is time-ordered from top to bottom,
The SQM starts the process of a service that should be started. At startup, the Main () method is called. In the Main () method of the sample service, the Run () method of the base class ServiceBase is called. Run ( ) registers the method ServiceMainCallback ( \ using NitaveMethods. StartServiceCtrlDispatcher ( ) in the SCM and writes an entry to the event log.
Next, the SCM calls the registered method ServiceMainCallback ( ). in the service program. ServiceMainCallback () itself registers the handler in the SCM using NativeMethods. RegisterServiceCtrlHandler [Ex) () and sets the status of the service in the SCM. Then the OnStart () method is called. In On Start ( ), you need to implement the startup code. If On Start () is successful, the string “Service started successfully” is written to the event log.
The handler is implemented in the ServiceCommandCallback () method. The SCM calls this method when changes are requested from the service. The ServicecommandCallback () method routes the requests further to OnPause (), OnContinue (), OnStop (), OnCustomCommand (), and OnPowerEvent ( ) .
This section looks into the application wizard-generated main function of the service process. In the main function, an array of ServiceBase classes, ServicesToRun, is declared. One instance of the Quote Service class is created and passed as the first element to the ServicesToRun array. If more than one service should run inside this service process, it is necessary to add more instances of the specific service classes to the array. This array is then passed to the static Run () method of the ServiceBase class. With the Run () method of ServiceBase, you are giving the SCM references to the entry points of your services. The main thread of your service process is now blocked and waits for the service to terminate.
Here is the automatically generated code:
If there is only a single service in the process, the array can be removed; the Run() method accepts a single object derived from the class ServiceBase, so the Main () method can be reduced to this:
The service program Services. exe includes multiple services. If you have a similar service, where more than one service is running in a single process in which you must initialize some shared state for multiple services, the shared initialization must be done before the Run () method. With the Run( ) method, the main thread is blocked until the service process is stopped, and any following instructions would not be reached before the end of the service. The initialization shouldn’t take longer than 30 seconds. If the initialization code were to take longer than this, the SCM would assume that the service startup failed. You need to take into account the slowest machines where this service should run within the 30-second limit. If the initialization takes longer, you could start the initialization in a different thread so that the main thread calls Run() in time. An event object can,then be used to signal that the thread has completed its work.
At service start, the OnStart () method is called. In this method, you can start the previously created socket server. You must reference the QuoteServer assembly for the use of the QuoteService. The thread calling OnStart () cannot be blocked: this method must return to the caller, which is the ServiceMainCallback () method of the ServiceBase class. The ServiceBase class registers the handler and informs the SCM that the service started successfully after calling OnStart ():
When the service is stopped, the OnStop () method is called. You should stop the service functionality in this method:
In addition to OnStart () and OnStop ( ), you can override the following handlers in the service class:
¤ OnPause () is called when the service should be paused.
¤ OnContinue () is called when the service should return to normal operation after being paused. Tomake it possible for the overridden methods OnPause () and OnContinue () to be called, the CanPauseAndContinue property must be set to true.
¤ OnPowerEvent () is called when the power status of the system changes. The information about the change of the power status is in the Argument of type PowerBroadcastStatus. PowerBroadcastStatus is an enumeration with values such as Battery Lowand PowerStatusChange. Here, you will also get information if the system would like to suspend (QuerySuspend), where you can approve or deny the suspend. You can read more about power events later in this chapter.
¤ OnCustomCommand() is a handler that can serve custom commands that are sent by a service control program. The method signature of OnCustomCommand() has an int argument where you get the custom command number. The value can be in the range from 128 to 256; values below 128are system-reserved values. in your service, you are re-reading the quotes file with the custom command 128:
Threading and Services
As stated earlier, the SCM will assume that the service failed if the initialization takes too long. To deal with this, you need to create a thread.
The OnStart () method in your service class must return in time. If you call a blocking method like AcceptSocket () from the TcpListener class, you need to start a thread for doing this. With a networking server that deals with multiple clients, a thread pool is also very useful. AcceptSocket ( ) should receive the call and hand the processing off to another thread from the pool. This way, no one waits for the execution of code and the system seems responsive.
A service must be configured in the registry. All services can be found in HKEY_LOCAL_MACHINE\ System\CurrentControlSet \Services. You can view the registry entries by using Regedit. The type of the service, display name, path to the executable, startup configuration, and so on are all found here. Figure 23-11shows the registry configuration of the W3SVCservice.
This configuration can be done by using the installer classes from the System. ServiceProcess namespace, as discussed in the following section.
You can add an installation program to the service by switching to the design view with Visual Studio and then selecting the Add Installer option from the context menu. With this option, a new ProjectInstaller class is created, and a ServiceInstaller and a ServiceProcessInstaller instance are created.
Figure 23-12shows the class diagram of the installer classes for services.
With this digram in mind, let’s go through the source code in the file ProjectInstaller. cs that was created with the Add Installer option.
The Installer Class
The class ProjectInstaller is derived from System. Configuration. Install. Installer. This is the base class for all custom installers. With the Installer class, it is possible to build transaction based installations. With a transaction-based installation, it is possible to roll back to the previous state if the installation fails, and any changes made by this installation up to that point will be undone. As you can see in Figure 23-12,the Installer class has Install (), Uninstall (), Commit (), and Rollback () methods, and they are called from installation programs.
The attribute [R Uninstaller (true) I means that the class ProjectInstaller should be invoked when installing an assembly. Custom action installers, as well as install util. exe (which is used later in this chapter), check for this attribute.
Similarly to Windows Forms applications, Initiali zeComponent () is called inside the constructor of the ProjectInstaller class:
The ServiceProcessinstaller and Service installer Classes
Within the implementation of InitializeComponent (), instances of the ServiceProcessInstaller class and the ServiceInstaller class are created. Both of these classes derive from the ComponentInstaller class, which itself derives from Installer.
Classes derived from ComponentInstaller can be used with an installation process. Remember that a service process can include more than one service. The ServiceProcessInstaller class is used for the configuration of the process that defines values for all services in this process, and the ServiceInstaller class is for the configuration of the service, so one instance of Service Installer is required for each service. If three services are inside the process, you need to add ServiceInstaller objects – three ServiceInstaller.instances are needed in that case:
ServiceProcesslnstaller installsan executable that implements the class ServiceBase. ServiceProcesslnstaller has properties for the complete process. The following table explains the properties shared by allthe services inside the process.
The process that isused to run the service can be specified with the Account property of the ServiceProcesslnstaller class using the ServiceAccount enumeration. The followin&table explains the differentvalues of the Account property.
ServiceInstaller is the class needed for every service; it has the following properties for each service inside a process: StartType, DisplayName, ServiceName, and ServicesDependentOn, as described in the following table.
In the testing phases, set StartType to Manual. This way, if you can’t stop the service (for example, when it has a bug), you still have the possibility to reboot the system. But if you have StartType set to Automatic, the service would be started automatically with the reboot! You can change this configuration at a later time when you are sure that it works.
The ServlceinstallerDialog Class
Another installer class in the System. ServiceProcess. Design namespace is ServiceInstallerDialog. This class can be used if you want the System Administrator to enter the username and password during the installation.
If you set the Account property of the class ServiceProcessInstaller to Service Account. User and the Username and Password properties to null, you will see the Set Service Login dialog box at installation time (see Figure 23-13).Youcan also cancel the installation at this point.
After adding the installer classes to the project, you can use the installutil. exe utility to install and uninstall the service. This utility can be used to install any assembly that has an Installer class. The installutil”. exe utility calls the method Install () of the class that derives from the Installer class for installation, and uninstall () for the uninstallation.
The command-line inputs for the installation and uninstallation of our service are:
After the service has been successfully installed, you can start the service manually from the Services MMC (see the next section for further details), and then you can start the client application. Figure 23-14 shows the client accessing the service.