February 18, 2010

Debugging Windows Services in C#

Well it's been a while since I had anything to blog about; rather, it's been a while since I've had the time to blog about anything. Recently @JohnMacIntyre commented that I should post regarding a technique that I've long used for developing and debugging Windows Services in C# which frankly are a pain in the ass when not designed with this purpose in mind.

My technique is simple, and it didn't cross my mind that other people weren't doing this or something similar:

  • Design your working class - this class actually does any work. It will be instantiated and configured by your application shell before being pushed into your service class.
  • Design your service class - Your service class is going to receive your working class via its constructor and when told to start will tell your working class to do its work.
  • Design your shell application - This could be a Windows Service shell, or a console app, windows forms app, whatever, it's not important. The point is that your shell application will do very little at all. It will read your configuration file for any necessary settings before instantiating and configuring your working class, and then instantiating and configuring your service class, pushing your working class into it. From here, your shell application only tells your service class to start or stop. Your service class will handle the rest.

Sounds simple enough - if a little um... vague. So let me clarify with an example - here's a basic design brief that demonstrates:

Design a service that will poll some web service every 30 seconds for information and push the received information into a database.

So where do I start?

Okay, here's my architecture:

Class 1 [Working Code]: This class will connect to the web service and retrieve the information and push it into my database - once. It doesn't handle the timed polling, it doesn't handle anything but calling the service, retrieving the information and storing that information in the database.

Class 2 [Service Code]: This is a class will initiate a timer at an interval specified in the constructor. When instructed to start, at the specified interval, it will tell Class 1 to do its thing.

Class 3 [GUI/HMI/Windows Service Shell]: This could be any wrapper, a console application, a Windows Service, a Windows Application, an ASP.NET application it doesn't matter. It's going to instantiate Class 1 and push it into the constructor of Class 2. It will then tell Class 2 to start, at some later time, it will tell Class 2 to stop and clear up after itself. The point is that Class 1 and Class 2 are important, and Class 3 is just a holding cell for them.

A little psuedo code to demonstrate:

Class 1: Working Code

public class InformationWorker
{
 private string _webServiceUrl;
 public string WebServiceUrl
 {
  get
  {
   return _webServiceUrl;
  }
 }
 private string _webServiceUserName;
 public string _webServiceUserName
 {
  get
  {
   return _webServiceUserName;
  }
 }
 private string _webServicePassword;
 public string WebServicePassword
 {
  get
  {
   return _webServicePassword;
  }
 }
 private string _dbConnection;
 public string DataConnectionString
 {
  get
  {
   return _dbConnection;
  }
 }
 public InformationWorker(string webServiceUrl, string webServiceUserName, string webServicePassword, string dbConnectionString)
 {
  _webServiceUsrl = webServiceUrl;
  _webServiceUserName = webServiceUserName;
  _webServicePassword = webServicePassword;
  _dbConnection = DataConnectionString;
 }
 public void RetrieveAndStore()
 {
  RawInformation ri = GetInformationFromWebService(WebServiceUrl, WebServiceUserName, WebServicePassword);
  StoreRawInformation(DataConnectionString, ri);
 }
 private RawInformation GetInformationFromWebService(string WebServiceUrl, string WebServiceUserName, string WebServicePassword)
 {
  //Omitted for brevity;
 }
 private StoreRawInformation(string connectionString, RawInformation ri)
 {
  //Omitted for brevity;
 }
}

Class 2: Service Code

public class PollingService : IDisposable
{
 private _timer;
 private Action _eventAction;
 public PollingService(int TimerInterval, Action eventAction)
 {
  _timer = new Timer(TimerInterval);
  _timer.Elapsed += TimerElapsed; 
  _eventAction = eventAction;
 }
 public void TimerElapsed(object sender, TimerElapsedEventArgs e)
 {
  if (_eventAction != null)
   _eventAction();
 }
 public Start()
 {
  if (_timer.Interval > 0)
   _timer.Start();
 }
 public Stop()
 {
  if (_timer.Enabled)
   _timer.Stop();
 }
 private bool _disposed;
 private void Dispose(bool disposing)
 {
  if (disposed || !disposing)
   return;
  
  //Kill the timer;
  if (_timer != null)
  {
   if (_timer.Enabled)
    _timer.Stop();
   _timer.Elapsed -= TimerElapsed;
   _timer.Dispose();
  }
  disposed = true;
 }
 public void Dispose()
 {
  Dispose(true);
  GC.SuppressFinalize(this);
 }
}

Consider this a console application, I'm not going to reference a config file but configure programmatically - it makes no difference for the purpose of demonstration. This could equally be any other type of application shell.

public class ShellClass
{
 public void Main(string[] args)
 {
  string WebServiceUrl = "http://www.webservices.com/MyWebService";
  string UserName = "ben.alabaster@webservices.com";
  string Password = "SomeRand0mPa55w0rd";
  string DatabaseConnection = "Data Source=localhost;Database=SomeRandomDatabase;Integrated Security=SSPI";
  InformationWorker iw = new InformationWorkder(WebServiceUrl, UserName, Password, DatabaseConnection);
  
  int TimerInterval = 30000; //Every 30 seconds;
  
  using (PollingService ps = new PollingService(TimerInterval, iw.RetreiveAndStore))
  {
   ps.Start();
  
   Console.ReadKey();
  
   ps.Stop();
  }
 }
}

You can now test your service code without having to step through painful WinDbg sessions and attaching to your web service in the first seconds of startup. When you've finished debugging you just transform your console/GUI code into a Windows Service:

public class WindowsService
{
 private PollingService _ps;

 public void OnStart()
 {
  string WebServiceUrl = "http://www.webservices.com/MyWebService";
  string UserName = "ben.alabaster@webservices.com";
  string Password = "SomeRand0mPa55w0rd";
  string DatabaseConnection = "Data Source=localhost;Database=SomeRandomDatabase;Integrated Security=SSPI";
  InformationWorker iw = new InformationWorker(WebServiceUrl, UserName, Password, DatabaseConnection);
  
  int TimerInterval = 30000; //Every 30 seconds;
  _ps = new PollingService(TimerInterval, iw.RetrieveAndStore);
  _ps.Start();
 }

 public void OnStop()
 {
  _ps.Stop();
  _ps.Dispose();
 }
}