There are three programmer-to-programmer topics in this chapter: Windows services, Web services, and how to deploy applications using Windows installer (.MSI) files. All these issues are important ones, and we'll use them to round off the GUI-oriented application coverage. We'll start with Windows services. Windows services are not typically front-line applications that the user runs and interacts with. Instead, they provide support services, often for device drivers, such as printer device drivers, audio devices, data providers, CD creation software, and so on. As such, Windows services don't need a real user interface as you see in standard Windows applications. They often do have a control panel-like interface that the users can open by clicking or right-clicking an icon in the taskbar, however. (You can create taskbar icons in Windows applications using the NotifyIcon control from the Windows Forms tab in the toolbox.) Users can customize, and even start or stop, a Windows service using that control panel. Other Windows applications can interact with the service at runtime; for example, SQL Server uses a Windows service to make it accessible to other applications. We'll see how to create a working Windows service in this chapter. In the FCL, Windows services are based on the ServiceBase class, and that class gives us most of the support we'll need. When you write a Windows service, you should override the OnStart and OnStop methodseven though their names imply they are event handlers, they're actually methods. These methods are called when the service starts and stops. You might also want to override the OnPause and OnContinue event handlers to handle occasions where the service is paused and resumed.
You can configure Windows services to start automatically when the computer starts, or you can start them manually using an administration tool built into Windows, the Service Control Manager (SCM). We'll see how this works in practice now. You can see an example, ch12_01, in the code for this book, and we'll take that application apart here. To follow along, create a new Windows service project. Choose File, New, Project in the IDE, and select the Windows Service icon in the Templates box of the New Project dialog box. Give this new service the name ch12_01 and click OK. This creates the new Windows service project in Figure 12.1; the default name for this new service is "Service1" . Figure 12.1. A new Windows service.
In our Windows service, we are going to write to our Windows service's event log when the OnStart and OnStop methods are called. To handle an event log, you must specify or create an event source . An event source registers your application with the event log as a source of data so the event log can listen for that data. You can give the event source as any string, but the name must be unique among other registered sources. In this example, we're going to register our event log in the Windows service's constructor, which you can find in the Component Designer generated code region of the Windows service's code, Service1.cs . That constructor looks like this now: public Service1() { // This call is required by the Windows.Forms Component Designer. InitializeComponent(); // TODO: Add any initialization after the InitComponent call } In this example, we create an event source named "CSSource1" . After the new source is created, we assign its name to the eventLog1 object's Source property like this: public Service1() { // This call is required by the Windows.Forms Component Designer. InitializeComponent(); if (!System.Diagnostics.EventLog.SourceExists("CSSource1")) { System.Diagnostics.EventLog.CreateEventSource("CSSource1", "currentLog1"); } eventLog1.Source = "CSSource1"; } Now we're ready to write to our event log when the service starts and stops. You can do that in the OnStart and OnStop methods, which look like this in Service1.cs currently: protected override void OnStart(string[] args) { // TODO: Add code here to start your service. } protected override void OnStop() { // TODO: Add code here to perform any tear-down necessary to // stop your service. } To write text to a Windows service's event log, you can use the log's WriteEntry method. In this case, we'll insert a message into the log indicating that the service started or stopped , like this: protected override void OnStart(string[] args) { eventLog1.WriteEntry("Starting ch12_01."); } protected override void OnStop() { eventLog1.WriteEntry("Stopping ch12_01."); } When our Windows service starts, our code will write "Starting ch12_01." to event log currentLog1 , and when the service stops, our code will write "Stopping ch12_01." to the log. We've created our Windows service. To install that service, we'll need an installer, so click the Service1.cs[Design] tab now to open the designer for Service1 . Make sure that eventLog1 in that designer does not have the focus (we want to create an installer for the service itself, not the event log object in the service), and then click the Add Installer link in the description section of the properties window (this link is visible at bottom right in Figure 12.2). Figure 12.2. Adding an event log to a Windows service.
This creates ProjectInstaller.cs with two objects in it, serviceProcessInstaller1 and serviceInstaller1 , as you see in Figure 12.3. Figure 12.3. Creating an installer for a Windows service.
ServiceInstaller objects inform Windows about a service by writing Windows Registry values for the service to a Registry subkey under the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services Registry key. The service is identified by its ServiceName value in this subkey. ServiceProcessInstaller objects handle the individual processes started by our service. When you install a Windows service, you have to indicate which account it should run under. In this example, we'll do that by clicking the serviceProcessInstaller1 object to give it the focus and setting its Account property to LocalSystem . Besides LocalSystem , you can also set this property to LocalService , NetworkService , or User . When you set Account to User , you must set the Username and Password properties of the serviceProcessInstaller1 object to configure this object for a specific user account. Now click the serviceInstaller1 object and make sure its ServiceName property is set to the name of this service, Service1 (it should already be set that way). You use the ServiceInstaller1 object's StartType property to indicate how to start the service. Here are the possible ways of starting the service, using values from the ServiceStartMode enumeration:
The safest of these while testing a new Windows service is Manual , so set the StartType property of the ServiceInstaller1 object to Manual now.
Installing a Windows ServiceThe next step is to build and install our new Windows service, Service1 . To build the service, select Build, Build ch12_01, which creates ch12_01.exe. To actually install the service in Windows, you can use the InstallUtil.exe tool that comes with the .NET Framework. In Windows 2000, for example, you can find InstallUtil.exe in the C:\WINNT\Microsoft.NET\Framework\ xxxxxxxx directory, where xxxxxxxx is the .NET Framework's version number. Here's how you install ch12_01.exe using InstallUtil.exe at the DOS command prompt (note that the command line here is too wide for the page, so it's split into two lines): C:\WINNT\Microsoft.NET\Framework\ xxxxxxxxxx >installutil c:\c#\ch12\ch12_01\bin\Debug\ch12_01.exe Microsoft (R) .NET Framework Installation utility Version xxxxxxxxxx Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Running a transacted installation. Beginning the Install phase of the installation. See the contents of the log file for the c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe assembly's progress. The file is located at c:\c#\ch12\ch12_01\bin\debug\ch12_01.InstallLog. Installing assembly 'c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe'. Affected parameters are: assemblypath = c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe logfile = c:\c#\ch12\ch12_01\bin\debug\ch12_01.InstallLog Installing service Service1... Service Service1 has been successfully installed. Creating EventLog source Service1 in log Application... The Install phase completed successfully, and the Commit phase is beginning. See the contents of the log file for the c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe assembly's progress. The file is located at c:\c#\ch12\ch12_01\bin\debug\ch12_01.InstallLog. Committing assembly 'c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe'. Affected parameters are: assemblypath = c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe logfile = c:\c#\ch12\ch12_01\bin\debug\ch12_01.InstallLog The Commit phase completed successfully. The transacted install has completed. C:\WINNT\Microsoft.NET\Framework\ xxxxxxxxxx > Now our Windows service has been installed. If InstallUtil hadn't been able to install the new service without problems, it would have rolled back the installation and removed the non-working service. Our new Windows service is installed, but not yet started, because we chose manual startup. In this case, we'll use the Service Control Manager to start our service. The SCM is part of Windows; for example, in Windows 2000, you can start the SCM this way:
You can see the Service Control Manager in Figure 12.4, and you can see our newly installed service, Service1 , listed in the SCM in the figure. Figure 12.4. The Service Control Manager.
To start our new service, right-click Service1 in the Service Control Manager now and select the Start item in the menu that appears. Doing so starts the service, as you see in Figure 12.5, where Service1 is listed as Started . Figure 12.5. Starting a Windows service.
To stop the service, right-click Service1 in the Service Control Manager and select the Stop item. We've been able to start and stop our new service, so it should have written to our event log, currentLog1 . You can check whether it has from inside the IDE; you just open the Server Explorer's Event Logs node as you see in Figure 12.6, and take a look at the entry for CSSource1 in currentLog1 . Here, you can see our service's two entries Starting ch12_01. and Stopping ch12_01. in the event log in the Server Explorer in Figure 12.6. (If you don't see the messages there, or messages don't appear when you start and stop the service a number of times, refresh the event log by right-clicking currentLog1 in the Server Explorer and selecting the Refresh item.) And that's itthe Windows service is a success. Figure 12.6. The Server Explorer.
Our Windows service did exactly what it was supposed toit wrote to an event log when it was started and stopped. Now that you can run code in a Windows service, you can see this is only the beginning. You can get the service's code started when the OnStart method is called, and run it in the background, supplying its service for as long as needed. Uninstalling a Windows ServiceYou can uninstall a Windows service, removing it from the SCM, with InstallUtil.exe; just use the /u option to uninstall. Here's what you see when you uninstall the Windows servicenotice that the command line is the same except for the /u : C:\WINNT\Microsoft.NET\Framework\ xxxxxxxxxx >installutil c:\c#\ch12\ch12_01\bin\Debug\ch12_01.exe /u Microsoft (R) .NET Framework Installation utility Version xxxxxxxxxx Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. The uninstall is beginning. See the contents of the log file for the c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe assembly's progress. The file is located at c:\c#\ch12\ch12_01\bin\debug\ch12_01.InstallLog. Uninstalling assembly 'c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe'. Affected parameters are: assemblypath = c:\c#\ch12\ch12_01\bin\debug\ch12_01.exe logfile = c:\c#\ch12\ch12_01\bin\debug\ch12_01.InstallLog Removing EventLog source Service1. Service Service1 is being removed from the system... Service Service1 was successfully removed from the system. The uninstall has completed. C:\WINNT\Microsoft.NET\Framework\ xxxxxxxxxx > Interacting with Windows Services from Other ApplicationsWe've seen how to run a Windows service in the background, but how do you connect to that Windows service from another application? All you need to do is drag a ServiceController object from the Components tab of the Toolbox to a form in a Windows application, creating a new object, serviceController1 . Then you set these properties in the properties window for this object:
Now using the power of the Windows service becomes easyyou can use the properties and methods exposed by the Windows service as though they were properties and methods of the serviceController1 object. For example, here's how you might use the CanStop and ServiceName properties of a Windows service as connected to by serviceController1 : if (serviceController1.CanStop) { MessageBox.Show(serviceController1.ServiceName + " can be stopped."); } You can also create a ServiceController object in code. To do that, you add a reference to the System.ServiceProcess DLL by right-clicking the current project in the Solution Explorer and selecting Add Reference. Then, click the .NET tab in the Add Reference dialog box, select System.ServiceProcess.dll and click Select. Finally, click OK to close the Add Reference dialog box. Also, you should include a using statement in your application's code for the System.ServiceProcess namespace. Now you can access the properties and methods that are built into a Windows service using the new ServiceController object like this: using System.ServiceProcess; . . . ServiceController controller1 = new ServiceController("Service1"); if (controller1.CanStop) { MessageBox.Show(controller1.ServiceName + " can be stopped."); } That's all it takes to access a Windows service from code. (Note that you can even implement events in a Windows service, and, using ServiceController objects, handle those events in other applications.) Next, we'll take a look at the properties, methods, and events of a few of the important Windows services classes to round off this topic. Working with the ServiceBase ClassThe ServiceBase class is the base class for Windows services, and you can find the significant public properties of ServiceBase objects in Table 12.1, and their significant protected methods in Table 12.2 (note that although the items in Table 12.2 look like events, they're actually methods you can override). Table 12.1. Significant Public Properties of ServiceBase Objects
Table 12.2. Significant Protected Methods of ServiceBase Objects
Working with the EventLog ClassThe EventLog class supports access to Windows event logs used by Windows services; you can find the significant public static methods of EventLog in Table 12.3, the significant public properties of EventLog objects in Table 12.4, their significant methods in Table 12.5, and their significant events in Table 12.6. Table 12.3. Significant Public Static Methods Properties of the EventLog Class
Table 12.4. Significant Public Properties of EventLog Objects
Table 12.5. Significant Public Methods of EventLog Objects
Table 12.6. Significant Public Events of EventLog Objects
Working with the ServiceProcessInstaller ClassAs we've already seen, ServiceProcessInstaller objects let you install specific processes in a Windows service. You can find the significant public properties of objects of the ServiceProcessInstaller class in Table 12.7, their significant methods in Table 12.8, and their significant events in Table 12.9. Table 12.7. Significant Public Properties of ServiceProcessInstaller Objects
Table 12.8. Significant Public Methods of ServiceProcessInstaller Objects
Table 12.9. Significant Public Events of ServiceProcessInstaller Objects
Working with the ServiceInstaller ClassYou use ServiceInstaller objects to install Windows services, and you can find the significant public properties of objects of this class in Table 12.10, their significant methods in Table 12.11, and their significant events in Table 12.12. Table 12.10. Significant Public Properties of ServiceInstaller Objects
Table 12.11. Significant Public Methods of ServiceInstaller Objects
Table 12.12. Significant Public Methods of ServiceInstaller Objects
That completes our look at Windows services; next up are Web services. |