I’m working on a Windows Service application at The Day Job, and while I’ve done the usual test-driven development approach, I still have a few things I’m testing and polishing that are integration-related (such as things behaving slightly differently when using SQL Server instead of a mock). Deploying a service isn’t difficult thanks to installutil, but it is still an extra hoop that I didn’t want to jump through as I tweaked my service. With .NET, services are mostly just regular Windows applications, the difference is in what happens inside of the Main method:
//For a service application...
static void Main(string[] args)
{
ServiceBase.Run(new[] { new MyAwesomeService() });
}
...
//For a WinForms application
static void Main(string[] args)
{
Application.Run(new MyWinForm());
}
//For a console application
static void Main(string[] args)
{
Console.WriteLine("Hello, world!");
//Do stuff;
return;
}
In a service application, the service class is registered using ServiceBase.Run. With a WinForms application, you would show your first form from Main. With a Console application, you would do some stuff and return from Main when its time to quit.
If you attempt to debug or otherwise start a service application directly, you will see the following message: “Cannot start service from the command line or a debugger…” If you want to run the service, you have to install it, which again, isn’t hard, but it takes longer than just pressing F5 does.
The solution is simple: allow your application to run either as a service OR as a regular application. I implemented this behavior by checking for the existence of a “—debug” argument. If set, the service runs as a regular application. When not set, it runs as a service. To make it easy to debug in Visual Studio, I added the “—debug” flag as an argument under the project properties “Debug” tab, so now pressing F5 or clicking “Debug” runs my service project in application mode.
Here’s how I implemented it:
static class Program
{
static void Main(string[] args)
{
if (args[0] == "--debug")
{
RunAsConsoleApplication();
}
else
{
RunAsService();
}
}
private static void RunAsConsoleApplication()
{
MyAwesomeService service = new MyAwesomeService();
service.Start();
ManualResetEvent blocker = new ManualResetEvent(false);
Console.CancelKeyPress += delegate
{
service.Stop();
blocker.Set();
};
blocker.WaitOne();
}
private static void RunAsService()
{
ServiceBase.Run(new[] { new MyAwesomeService() });
}
}
Note that I’m using a ManualResetEvent to block the thread until Ctrl+C is pressed. If you run your service as a WinForms application, you will never see a console, so you can’t press Ctrl+C to kill the application. Instead, you’ll have to use “Stop Debugging” to kill it. If however you set your project to run as a console application, you can press Ctrl+C to stop your service and shutdown the application just as if you had pressed the Stop button in the Windows service manager.
One other thing to note: my service class exposes Start and Stop methods that encapsulate the logic of OnStart and OnStop (which are what’s run when you start/stop the service from the Windows service manager).
The solution isn’t perfect, but it works. Anyone know of any alternatives?
Tags: