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?
I like it.
A variation of this that we’ve used for some older projects is to make the Service itself fairly dumb, and just have enough logic to launch a console app – which is actually doing whatever the service needed to do.
This has the advantages of preventing issues with the Console App bringing down the service, and ensuring any memory leaks we can’t fix (i.e third party code) are kept in check – the entire process is killed periodically.
@Will,
I like that approach. I actually thought about making a generic host service that could manage any number of console applications in a manner you’re describing. As this project advances forward and we start adding additional services, that will probably be the route I go.