C# Template Link
VB Template Link
Service Configuration
Services in general do not have a user interface. Now, it's true that I've seen many people write user interface forms for services, and yes, those forms are displayed on the console session. However, I consider this a bad design for services. You're service should not depend on any user interaction. The better way is to separate user input from service messaging. In this implementation of a windows service, I won't get into how you should enter or store configuration information. You can store it any number of ways: database, registry, local file, etc. How you put information into that store should be done with a separate application.
Base Template
Ok, now let's take a brief look at the service template that ships with visual studio. The base project contains the following files: Program.cs and Service.cs. There are several things that need to be done, and many of them are outlined in this msdn article. You should refer to this article for a detailed explanation of adding the installer objects. Also, the base template uses "Service1" as the class name for the service. I haven't changed that, but you can do the replacements yourself if you want to name it something else. Look at the general service properties to discover all the settings that will make your installed service easier to identify.
A quick note about security and impersonation. Since the service is not running in session it will have its own security context. I typically leave the default set to "local system". This is fine for most configurations. But if your service will access something on another machine (like a file share), then you will need to change it to run under another domain account that has permission to access whatever it is on the other machine. When this happens I usually have the network admin create a separate windows account for the service, and have them set the password to never expire. There's nothing worse than having your service stop working because the account password changed or expired. The general application eventlogs are very misleading when this happens.
Conditional Dependency on MSSQLServer
I made one addition to the installer code: a pre-installation routine that will make SQL server a service dependency if it exists. Most of the time my services need to access a database, and that's pretty much always SQL server. Now I may chose to install the service on an independent machine or the main server. If I'm putting it on a machine that has SQL server on it, then I will almost surely use the database on that machine. Therefore, it's useful to make the service dependant on SQL server.

Now when the machine reboots and the service (which is set to automatic start) starts up, it will make sure SQL server has started before my service starts. But if SQL server doesn't exist on the machine, then we don't want the dependency.
Event Logging
I find it very useful to make a separate eventlog for my service. You can by default write entries to the general application eventlog, but it's much more convenient to write to your own log. To do this you can use the System.Diagnostic.Eventlog object and all the associated code that goes with that, but I've found it useful to wrap that code in my own classes: SystemEventLogFactory and SystemEventLog. The factory uses a thread safe singleton pattern to deliver the instance of the eventlog that we will create. Each class can then make it's own pointer to this instance. Since we're really only writing eventlogs the threading isn't all that critical, but it's good practice nonetheless.
The other reason I make the wrapper class is that it provides me with a flagging system that I can use to distinguish between different kind of eventlogs. After you've had to live with these services for a while, you'll come to appreciate the idea of changing how much logging is needed. When my service is running fine, I'll turn back the logging to only create the minimum number of log entries that are needed. But invariably something happens down the road and I wish that I could get more information. By adding additional eventlogs it's kind of like having a debug.writeline that I can turn on or off as needed.
The SystemEventLog class has a public enumeration that you can redefine and extend. You'll notice that it uses the [Flags] modifier which allows us to use bitwise operators. So the MessageFlagComposite represents a single integer that is the bitwise or () of all the flags you want set. While I've hard coded the flags into the factory class for this template, you'll want to pull your composite setting from your configuration store.

Now when ever I use my mxLog to write an entry I can specify what flag is associated with that entry at design time. At run time that flag is compared to the MessageFlagComposite to see if that particular flag is actually set, and only writes the eventlog if it is. Thus we can add lots of eventlog entries into our code (and leave them there), but only write the ones we want at run time.
Finally, when choosing strings for LogName and Source, you should keep a couple things in mind. First, a source can only be used in one eventlog, the by default the general application log will your your compiled executable name as the source for this application. Therefore, you should not use a source name that is the same as your compiled executable. By default when you use this template the word "source" is appended to the end of your project name to use as the source. The logname is set to the "safe" version of your project name. Of course you can change these if you wish. Also by default I've set the machine name to ".". This assumes that you want your eventlog on the same machine where it has been installed. I've never known of a good reason not to do this. If you want to access the eventlog from another machine there are several ways to do that.
Main Service Code
The main service class now has two private class level objects: mxLog (an instance of our custom eventlog), and mxTimerBasedMonitor. The later just one type of class object that will carry out the work we want done by the service. I'll cover the several base types of monitor objects in follow up blog entries.
At this time the only thing to note is that all our work will be done on background threads to the main service thread. The main reason I do this is because it's the simplest way to quickly get through the OnStart event. Just about any help article you will read on services will stress how important it is that your OnStart event be optimized to complete quickly. The main reason for this is so that you won't have your service routinely winding up in the "starting" state. The WindowsOS only gives you 60 seconds to complete your OnStart event before it complains. This will lead the users of your code to think something is wrong when the reality is that you're just taking a long time to get things going.
For similar reasons I have a tendency to wrap all my methods in try/catch blocks. So you will note that the OnStart also has a try/catch block. The affect of this is that the service will always reach the started state. You will need to actually check the eventlog to see if it started successfully. There's nothing worse than a hung service that you have to kill because you hit an unhandled error in your code.
MonitorBase
Class MonitorBase is what all the different types of service working classes will inherit from. It makes an instance of the Log and give a method to start and stop the monitor. This is all well and good, but we haven't done anything yet!
We'll cover the different monitor classes in subsequent blogs... stay tuned.

0 comments:
Post a Comment