Thursday, February 4, 2010

Testing a Windows Service From Within Visual Studio

When working with Windows Services, you can’t set the windows service project as the “Startup Project” and start debugging. You get a Windows Service Start Failure dialog box telling you that you "Cannot start service from the command line or a debugger. A Windows Service must first be installed (using installutil.exe) and then started with the Server Explorer, Windows Services Administrative tool or the Net Start Command.

In order to get around this issue, a common approach I’ve seen is to add a windows form to the windows service project, and then in the project’s Program.Main() method, comment out the ServiceBase.Run() and add this code to run the windows form instead:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());

This will result in your TestForm running instead of your actual service and you can add whatever features that you want to the TestForm Form to be able to debug your service (IMHO it’s also good to add some cleanup functionality to this test form, like reprocess transaction 9999). If the Windows Service ever needs to be rebuilt, the changes to Program.cs have to be reverted. The result is a Program.cs file that get checked into source control looking like this:

static class Program

/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;

// More than one user Service may run within the same process. To add
// another service to this process, change the following line to
// create a second service object. For example,
//
// ServicesToRun = new ServiceBase[] {new Service1(), new MySecondUserService()};
//
ServicesToRun = new ServiceBase[] { new Service() };

ServiceBase.Run(ServicesToRun);

//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new TestForm());

}
}

and being changed to look like this:

static class Program

/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;

// More than one user Service may run within the same process. To add
// another service to this process, change the following line to
// create a second service object. For example,
//
// ServicesToRun = new ServiceBase[] {new Service1(), new MySecondUserService()};
//
ServicesToRun = new ServiceBase[] { new Service() };

//ServiceBase.Run(ServicesToRun);

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());

}
}

whenever it needs to be ran in Visual Studio. This can become really annoying if you’re doing this multiple times a day on different services, or your source control provider doesn’t support multiple simultaneous checkouts. It can be even worse if the build for the service takes a long time, and you forget to comment/uncomment the right lines, and there is always the risk that you check in the Windows Form version of the service.

So, because I’m a lazy programmer (If I have to do something twice, that’s one too many times) I figured out a solution utilizing the System.Diagnostics.Debugger.IsAttached property. I created a simple static class with a static constructor that accepts two parameters. One for the services that should be ran if we aren’t starting this in Visual Studio, and another for the Windows Form Type to use as the test form if debugging. It ended up looking like this:

public static class ServiceApplication

/// <summary>
/// Deteremines if the service is being ran in debug mode.
/// If it is, the testFormType is ran, if not, the service is started normally
/// </summary>
/// <param name="servicesToRun">The services to run</param>
/// <param name="testFormType">The Form type that is the test form for the service</param>
public static void Run(ServiceBase[] servicesToRun, Type testFormType)
{
if (System.Diagnostics.Debugger.IsAttached)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form testForm = (Form)Activator.CreateInstance(testFormType);
Application.Run(testForm);
}
else
{
ServiceBase.Run(servicesToRun);
}
}
}

And now my Program.cs looks like this:

static class Program

/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
ServiceApplication.Run(new ServiceBase[] { new Service() }, typeof(TestForm));
}
}

No more editing the program.cs file! It just works the way I want it to!

2 comments:

Brian Jasmer said...

Excellent, Thank you!

I converted to VB and it works flawlessly!

A big time saver while debugging my services!

-Brian Jasmer

Daryl said...

Glad it helped!