Monday, January 28, 2008

Windows Services in C#: Part 1: Programming a Windows Service in C#

Part of a project I recently completed involved developing a custom email queue Windows Service in C# on the .NET 2.0 Framework. The application itself was quite simple. Unfortunately it had been some time since I wrote a Windows Service, and I hadn't written one on the 2.0 Framework and couldn't really remember much about it from the 1.1 days.

To the internet I went. I found lots of helpful articles on how to write the service, but not so many on how to start it after install, how to debug it, or how to add it to the uninstaller. However with a little imaginative searching and guesswork I managed to overcome. It sure would have helped me to have all of these topics in one place, so I figured I'd do a collection of articles for anyone out there struggling with the same issues.

This article is the first in a five-part series covering the following topics:
  1. Programming a Windows Service in C#
  2. Adding an Installer for Your Windows Service
  3. Getting Your Installer to Start Your Service
  4. Some Options for Debugging Your Windows Services
  5. Adding an Uninstaller for Your Windows Service
The examples in this series are written in C#, but this should help anyone out there wanting to do this on the .NET 2.0 Framework no matter if they're using C#, VB.Net, or any other language. This is particularly true with this subject since most of the work is actually done in the Visual Studio 2005 GUI and not in the code.

Let's get started!

Programming a Windows Service in C#

The obvious first step is to program our Windows Service. Our project is going to write a message to the Application Log when the service starts, stops and at every 5 minute interval while running. We'll do this with the help of the Timer object, so you might also learn a thing or two about the Timer object in the process. Plus, we will write a slick little helper function for writing to the Application Event Log.

1. Select New Project... under the File menu.
This will add a new project to your Solution. But, if you didn't know that already we're in trouble!




2. Select a Windows Service Project.
Under Visual C# > Windows, select Windows Service. At this point you should also give your project a name. For my examples my project will be called SuperService. Click Ok when you're done.



Since I'm not adding this project to a solution, a new solution of the same name will also be added for my project to reside in.

3. Delete Service1.cs and add your own.
It's rather unlikely that you want your service to be called "Service1", so let's delete Service1.cs and add our own Service. After deleting Service1.cs, right-click on your project in the Solution Explorer and select Add > New Item... From the menu.



In the resulting window, select Windows Service and give your service a name (mine is Logger.cs), as seen in the image below. Click Ok and your new service will be added to your project.

Now that you have the Windows service you want, you need to indicate that it is to be run. Open Program.cs in code view and find the line similar to the one below:


ServicesToRun = new ServiceBase[] { new Service1() };


Replace Service1 with whatever your new service is called.

4. Add your Timer to the Windows Service.
Drag and drop a Timer object from your Toolbox into the design view of your new service. It will probably be found under the Components category.



5. Set up your service and Timer's properties and events.
Click the timer you just added (probably displayed as timer1) and set its interval property to 300000 (that's 5 x 60 x 1000 for 5 minutes in milliseconds.)

Go into code view mode for your service.

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

namespace SuperService
{
partial class Logger : ServiceBase
{
public Logger()
{
InitializeComponent();
}

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.
}
}
}

In there you'll see that two overrides for your service have already been added, OnStart and OnStop. Naturally, OnStart runs when when you first start your service and the OnStop runs when your service is stopped - Be it by a user, the system or by another program.

Each time your service is started all of the code in your OnStart event must be executed. When starting a Windows Service, Windows only gives a fairly small amount of time for the service to begin before it times out. If your application can take a while to run, you will want the smallest possible footprint for your OnStart event - Both to save the user from having to wait for the service to start and to ensure it doesn't time out.

One of the great things about using a Timer with a Windows Service is that its counter runs in a separate thread. So, in your OnStart event only assign your EventHandlers and start your Timer. Perform the actual functionality of your service from the Timer's tick event. This will allow for a quick startup of your service.

For our example, our project is just going to write an event to the Event Log when the service starts and stops and when the timer iterates. I wrote a basic helper function to simplify the actual writing to the Event Log. You can copy the code below and paste it into your class:

static void LogEvent(String Message, EventLogEntryType type)
{
String source = "Logger";
String log = "Application";
if (!EventLog.SourceExists(source))
{
EventLog.CreateEventSource(source, log);
}

EventLog eLog = new EventLog();
eLog.Source = source;

eLog.WriteEntry(Message, type);
}
Logging to the Event Log is a bit out of the scope of this article, but I do want to point out one line:
String source = "Logger";
This string specifies what you want displayed for the Source column in the Event Log.

Now, let's update our service's OnStart and OnStop events to write to the Event Log:

protected override void OnStart(string[] args)
{
LogEvent("This SuperService has started!", EventLogEntryType.Information);
}

protected override void OnStop()
{
LogEvent("This SuperService has stopped.", EventLogEntryType.Information);
}
Now we'll have entries added to the Event Log when the service starts and stops, but what about our timer?

namespace SuperService
{
partial class Logger : ServiceBase
{
public Logger()
{
InitializeComponent();
}

void timer1_Tick(object sender, EventArgs e)
{
LogEvent("This Timer has been ticked!", EventLogEntryType.Information);
}

protected override void OnStart(string[] args)
{
timer1.Tick += new EventHandler(timer1_Tick);
timer1.Start();
LogEvent("This SuperService has started!", EventLogEntryType.Information);
}

protected override void OnStop()
{
LogEvent("This SuperService has stopped.", EventLogEntryType.Information);
}

protected override void OnPause()
{
base.OnPause();
timer1.Stop();
}

protected override void OnContinue()
{
base.OnContinue();
timer1.Start();
}

static void LogEvent(String Message, EventLogEntryType type)
{
String source = "Logger";
String log = "Application";
if (!EventLog.SourceExists(source))
{
EventLog.CreateEventSource(source, log);
}

EventLog eLog = new EventLog();
eLog.Source = source;

eLog.WriteEntry(Message, type);
}
}
}

For this we add an event handler for the tick event of our Timer to the OnStart function of our Service. You might say to yourself, "But what if the service is started and stopped multiple times? Won't the event be excessively re-hooked?" In short, no. When you stop the service its thread closes, effectively killing off the hook. This is also why we don't need to stop the timer in the service's OnStop function. It will be killed when the service stops.

Additionally, we added a Timer1.Start() call to the OnStart function, to get our Timer rolling. We also added OnPause and OnContinue overrides, with Timer1.Stop() and Timer1.Start() calls, respectively.

That sums up our introduction to programming a Windows Service. In Part 2 we discuss
how to add an installer that will install our Windows Service. This gives us a chance to test out our code and simplifies the process of adding the service to other machines.

Continue to Part 2: Adding an Installer for Your Windows Service

Labels:

11 Comments:

At 1/31/08 4:34 PM, Blogger Scott Anderson said...

Nice article Grinn. There's definitely some useful information here. I look forward to the next parts!

- Scott Anderson

 
At 2/2/08 10:35 AM, Blogger Grinn said...

Thanks for the feedback, Scott. I'll be working on Part 2 today and hopefully have it up soon.

 
At 2/12/08 9:02 AM, Blogger Jeff said...

Nice article :)

I ended up having to use a System.Threading.Timer as the System.Windows.Forms.Timer was not firing at all :(

Found others had the same problem too... http://weblogs.asp.net/sibrahim/archive/2004/01/13/58429.aspx

 
At 2/12/08 9:10 AM, Blogger Grinn said...

Thanks for the head's-up, Jeff. Perhaps I'll update the article to instead use System.Threading.Timer.

 
At 4/22/08 8:34 AM, Blogger Sanj said...

Nice article series and just what I was looking for, thank you

One small thing is in VS 2008 atleast I would simply rename Service1 instead of deleting and re-adding it, as it also does refactoring like renaming related classes.

 
At 4/22/08 10:48 AM, Blogger Grinn said...

Thanks for the tip, Sanj. That would save some time. I didn't even realize '08 did that!

 
At 4/2/09 1:17 AM, Blogger Tharindu said...

When I installing i received a input dialog box to enter user name and passwords,confirm passwords.

What was it?
I unable to install service because of that
Pls help me

 
At 4/3/09 10:32 AM, Blogger Sanj said...

Tharindu, that would be your Windows user name and password log on credentials.

 
At 6/1/09 10:32 AM, Blogger Mario said...

Nice article, Thanks!

 
At 6/26/09 10:43 AM, Blogger Ramalingappa said...

Nice article.

 
At 3/18/10 4:27 PM, Blogger Tyler said...

You da man. Appreciate the knowledge!

 

Post a Comment

Links to this post:

Create a Link

<< Blog Home