Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Creating Windows Services
How to Create Windows Services

It's not uncommon for an enterprise application to need some form of background processes to do continuous work. These could be tasks such as

  • Cleanup abandoned shopping carts
  • Delete temporary files left behind by some report or image generation feature
  • Send email notifications
  • Create daily reports and send them out
  • Check an email inbox
  • Pull messages from a queue
  • Perform daily or monthly archiving
  • etc

For many of these things there are dedicated tools that provide that feature, like a reporting service (SSRS or BO,) scripts that run in the email server, or even simple executables that are fired by the Windows Task Scheduler. When you have only one or two background tasks, using something like the task scheduler may be OK, but administration quickly becomes painful when the number of tasks grows. The dedicated services like SSRS or BO can be overkill depending on the size of your application or organization.

One approach I like to take is to create a Windows Service for the application, grouping all the different background tasks under a single project, a single .exe, and a single configuration file. Visual Studio has always had a Windows Service project type, but the process of creating a working service is not as simple as you would hope, especially when your service performs more than one independent task.

After creating a couple of services, I realized that I definitely needed to stash all that monkey work somewhere I could just reuse later. I decided to create a helper library to assist creating and maintaining Windows services.

The library doesn't help with all kinds of Windows services, but has helped me a lot with the type of tasks I explained above. The key to the library is the ITask interface.

public interface ITask: IDisposable
{
    bool Started { get; }
    string Name { get; }
    void Start();
    void Stop();
    void Execute();
}

This interface shown all that is needed to create a task that can be started, stopped, and invoked by the service process. But this interface has too many members and many tasks are common enough that these members will be implemented almost identically. For example, tasks that execute on a regular interval will be almost identical, the only different member will be the Execute method. That's why the library comes with some handy base classes as shown in this diagram.

Now when I need to implement a task that runs repeatedly I simply inherit a task from PeriodicalTask or ScheduledTask as seen below. These classes will be part of my service project, from which I remove all the other classes that were added by default.

class CleanupTask : PeriodicalTask
{
    readonly static log4net.ILog Log =
        log4net.LogManager.GetLogger(
           System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public override void Execute()
    {
        //time to run....
        //TODO: write the actual code here
        // ShoppingCart.DeleteAbandonedCarts();
        Log.InfoFormat("Executed: {0}", this.GetType().Name);
    }
}


class DailyReportTask : ScheduledTask
{
    readonly static log4net.ILog Log =
        log4net.LogManager.GetLogger(
              System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    protected override void Execute(DateTime scheduledDate)
    {
        //time to run....
        //TODO: write the actual code here
        // SalesReport.SendDailySummary();
        Log.InfoFormat("Executed: {0}", this.GetType().Name);
    }
}

Instead of hard coding the interval or the scheduled time of the above tasks, we use the service's .config file for that:

<WindowsService>
    <tasks>
        <task name="CleanupTask" interval="600"  />
        <task name="DailyReportTask" time="21:30"  />
    </tasks>
</WindowsService>

There are only a few more things we need to do to get this service ready. First we need to add a new WindowsService item. Here we are naming it MyAppService and making it inherit from from SPServiceBase.

partial class MyAppService : SPServiceBase
{
    public const string MyAppSvcName = "MyAppSVC";
    public MyAppService()
    {
        InitializeComponent();
        //Important.. use the constant here AFTER 
        //   the call to InitializeComponent()
        this.ServiceName = MyAppSvcName;
    }
}

We also need to add an Installer Class, which I'll name simply Installer and which will be invoked during the service installation phase to add the appropriate registry entries to make the service be listed in the Services applet. Here's how this class looks like. Note that it inherits from another base class from the library.

[RunInstaller(true)]
public class Installer : SergioPereira.WindowsService.ServiceInstaller
{
    //That's all we need. Hooray!
}

I mentioned that the installer will add the necessary registry information. Some of that are the name and description of the service. We provide that with an assembly attribute that you can put in the AssemblyInfo.cs or anywhere you like in a .cs file (outside any class or namespace.)

[assembly: ServiceRegistration(
    SampleService.MyAppService.MyAppSvcName, // <-- just a string constant
    "MyApp Support Service",
    "Supports the MyApp application performing several " + 
           "critical background tasks.")
]

A Windows service is compiled as an .exe, so it needs an en entry point, a static Main function. Let's add a Program.cs like this:

class Program
{
    static void Main(string[] args)
    {
        if (!SelfInstaller.ProcessIntallationRequest(args))
        {

            MyAppService svc = new MyAppService();

            svc.AddTask(new CleanupTask());
            svc.AddTask(new DailyReportTask());
            //add more tasks if you have them

            svc.Run();
        }
    }
}

The code above is pretty simple, we are creating the tasks and telling our service to take care of them. Then we start the service. The interesting thing is the call to ProcessIntallationRequest. This is where we added the self-installing capability of the service. If you wrote a service in the past, you know that they get installed by using InstallUtil.exe. One potential problem is that InstallUtil.exe may not be present on the server or not in the PATH, making an scripted installation a little more complicated. Instead, by using the that call from SelfInstaller, we enabled our service to be invoked like the following to install or uninstall it (remember to execute as an Administrator).

SampleService.exe -i[nstall]
SampleService.exe -u[ninstall]

After installing it, you should see the service in the Services applet.

Here's the final structure of our project.

If you want, download the library source code along with a sample service project. There's more in the library than I have time to explain here, including an auto-update task and extra configuration properties for each task.


Posted 03-31-2008 12:08 AM by sergiopereira
Filed under: , ,

[Advertisement]

Comments

Reflective Perspective - Chris Alcock » The Morning Brew #62 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew #62
on 03-31-2008 3:14 AM

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #62

Igor Brejc wrote re: Creating Windows Services
on 04-02-2008 7:02 AM

Just to complement your code: I wrote some instructions on how to add multiple instance support to Windows services on igorbrejc.net/.../multiple-instance-windows-service-in-c

sergiopereira wrote re: Creating Windows Services
on 04-02-2008 8:48 AM

@Igor

That's nice. I think that if you need that, you can probably change the self-installing feature in my library to add support for passing the service name at the command line.

stephenpatten wrote re: Creating Windows Services
on 04-16-2008 4:55 PM

Sergio,

Thank you for the EXCELLENT code!

Stephen

RM wrote re: Creating Windows Services
on 11-19-2008 5:23 AM

Excellent code. I really appreciate your gesture of sharing this with the developer's community. Thanks once again.

Emmanuel wrote re: Creating Windows Services
on 12-03-2008 9:11 PM

Well Done !

Brad Mead wrote re: Creating Windows Services
on 02-06-2009 1:42 PM

Sergio...

Just found this through a comment on Dru Seller's post on the same topic. Good stuff. Just what I need in many ways.

See you in Seattle!

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)