views:

50

answers:

1

I'm in the process of designing the architecture of an application I’m planning on building and need some advice on the best way to implement a specific windows service component described below. I'll be building the service using .net 4.0 so I can take advantage of the new parallel and task APIs, I’ve also looked at using the MSMQ service however I’m not sure this is appropriate for what I hope to achieve.

The simplest way of explaining my use case is that users can create a number of reminders of different types for a task that they need to complete, which they create using a web-based application built in ASP.NET MVC 2. These reminders can be of various types for example email and SMS, which of cause must be sent at the specified due time. The reminders can be changed up until the point they have been sent to the user, paused and cancelled all together, which I guess makes a queuing based service such as MSMQ not appropriate?

I plan to host a windows service that will periodically (unless there is a more appropriate way?) check to see if there are any reminders due, determine their type and pass them to the specific component to deal with them and send them off. If an exception occurs the reminder will be queued up at a set interval and tried again, this will continue to happen with the interval increasing until they meet a set threshold at which point they are discarded and logged as a failure. To add a final layer of complexity to the service, I hope to specify in a configuration file the concrete implementation of each type (This means I can say change the SMS service due to cost or whatever), which are loaded at service start-up dynamically. Any reminders of an unknown or unavailable type will of cause automatically fail and be logged as before.

Once a reminder has been successfully sent it simply discards it, however with the SMS gateway I’m planning to use, it requires me to call its API to find out whether the message was successfully delivered or not, which means an additional timer is required at a set interval to check for this. It would also be nice to be able to add additional reminder type services that conform to a unified interface at service start-up without the need to change its code.

Finally, I don't know whether this should be posted as a separate question or not but I wondered would it be possible to say build a console application that could be started/stopped at anytime and when running can see what the windows service is currently doing?

This is my first ever question on Stackoverflow, even though I’ve been using the community for a while so I apologise if I’ve done some incorrectly.

Thanks in advance, Wayne

A: 

For the second part of your question, I have been thinking about this and here is a class I put together that helps to create a service which can be run both as a Console application as well as a Windows Service. This is fresh off the press, so there might be some issues to resolve, and some refactoring required esp. around the reflection code.

NB: You should set the Service project Output type to Console Application, this will still work fine as a normal service.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;

namespace DotNetWarrior.ServiceProcess
{  
  public class ServiceManager
  {
    private List<ServiceBase> _services = new List<ServiceBase>();

    public void RegisterService(ServiceBase service)
    {
      if (service == null) throw new ArgumentNullException("service");
      _services.Add(service);
    }

    public void Start(string[] args)
    {
      if (Environment.UserInteractive)
      {
        foreach (ServiceBase service in _services)
        {
          Start(service, args);
        }
        Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
        Thread.Sleep(Timeout.Infinite);
      }
      else
      {
        ServiceBase.Run(_services.ToArray());
      }
    }    

    public void Stop()
    {
      foreach (ServiceBase service in _services)
      {
        Stop(service);
      }
    }

    private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
    {
      Stop();
      Environment.Exit(0);
    }

    private void Start(ServiceBase service, string[] args)
    {
      try
      {
        Type serviceType = typeof(ServiceBase);       

        MethodInfo onStartMethod = serviceType.GetMethod(
          "OnStart", 
          BindingFlags.NonPublic | BindingFlags.Instance, 
          null, 
          new Type[] { typeof(string[]) }, 
          null);

        if (onStartMethod == null)
        {
          throw new Exception("Could not locate OnStart");
        }

        Console.WriteLine("Starting Service: {0}", service.ServiceName);
        onStartMethod.Invoke(service, new object[] { args });
        Console.WriteLine("Started Service: {0}", service.ServiceName);
      }
      catch (Exception ex)
      {
        Console.WriteLine("Start Service Failed: {0} - {1}", service.ServiceName, ex.Message);
      }
    }

    private void Stop(ServiceBase service)
    {
      try
      {
        Type serviceType = typeof(ServiceBase);
        MethodInfo onStopMethod = serviceType.GetMethod(
          "OnStop", 
          BindingFlags.NonPublic | BindingFlags.Instance);
        if (onStopMethod == null)
        {
          throw new Exception("Could not locate OnStart");
        }
        Console.WriteLine("Stoping Service: {0}", service.ServiceName);
        onStopMethod.Invoke(service, null);
        Console.WriteLine("Stopped Service: {0}", service.ServiceName);
      }
      catch (Exception ex)
      {
        Console.WriteLine("Stop Service Failed: {0} - {1}", service.ServiceName, ex.Message);
      }
    }
  }
}

To use this, you can rip the standard code out of the Main entry point of the service and replace it with the following.

static void Main(string[] args)
{
  ServiceManager services = new ServiceManager();
  services.RegisterService(new Service1());

  services.Start(args);      
}

The services.Start() method will detect that the service is being run as an interactive application and manually invoke the OnStart method of all the registered services, once started the main thread goes to sleep. To stop the services, just press 'Ctrl+C` which will result in the Services being stopped by calling the OnStop method of the service.

Of course is the application is run as a Service by the SCM then everyhing works as a normal service. The only caveat is that the service should not require to be run with 'Allow service to interact with desktop' since this will make the service run in interactively even though it is run as a service. This can be worked around if required, but hey I only just wrote the code.

Monitoring and Starting/Stopping a Service

From the command line you can use the NET.EXE to Start/Stop a service

Start a service
net start <service name>

Stop a service
net stop <service name>

For managing a service from .NET you can use System.ServiceProcess.ServiceController

// Stop a service
System.ServiceProcess.ServiceController sc = new
  System.ServiceProcess.ServiceController("<service name>");
sc.Stop();

For general communication with the service other than what is provided through ServiceController I would suggest that you host a WCF service as part of your service, which you can then use to communicate with the service to query internal details specific to your service.

Handling the Scheduling

To be honest, I was hesitant to answer this aspect of the question since there are so many approaches each with there Pros/Cons. So I will just give some high level options for you to consider. You have probably thought this through already, but here are a few things off the top of my head

If you are using SQL Server to store the notifications.

Have an SP that you can call to retrieve the reminders that are due, then process the result to raise the reminders approriately.

With this SP you have some options

  1. Call the SP periodically from your service and process the reminders
  2. Have a SQL Job that periodically runs the SP and adds a reminder to a Service Broker Queue. Your Service can then subscribe to the Queue and process reminders as they appear on the Queue. The advantage of this approach is that you can scale out with multiple servers processing the reminder notification generation without any special coding, just add another server that runs your service and the messages will automatically be distributed between the two servers.

If you are not using SQL Server to store the reminders

You can still use a similar approach as for SQL Server. Of course the Windows Service can query the data store using what ever is approapriate and process the reminders. Getting this to scale is a little harder since you will need to ensure that multiple servers do not process the same reminder etc. but not a train smash.

I think that covers the gist of it, everything else is some variation on the above. Ultimately your decision would depend on the target volumes, reliability requirements etc..

Chris Taylor
@Chris Thanks for sharing the code, i was hoping to use the console application as a monitor to the running windows service, which i could start and stop at any point to "listen" in. However, you're code won't go wasted as it comes in handy for something else i was looking to do =) Thanks for you're help.
Wayne Haffenden
@Wayne, sorry I totally missed the point there. See the up comming update.
Chris Taylor
@Chris Thanks for the update, do you have any links or code for hosting a WCF service so i can get an internal status. The basic idea was to be able to run an app to see exactly what the service was doing at any given point in time, e.g. Processing message 1 of 2 etc.
Wayne Haffenden
@Wayne, fortunately it is very simple. The following should get you started http://msdn.microsoft.com/en-us/library/ms733069.aspx
Chris Taylor
@Chris Thanks for that I'll have a play around =) If i get no response from anybody regarding the other questions then i'll mark you're answer as the correct one. Thanks again for all you're help!
Wayne Haffenden
@Wayne, no problem glad to be of some help, esp. since I started out totally off track :). Anyway, I also added some thoughts on the scheduling, it is just a few high-level set of thoughts.
Chris Taylor
@Chris Thanks again, i'll give the service broker ago sounds a lot more like what i need with the scheduling and then if i still have no joy then i'll post some more specific questions on here! Thanks for all you're help throughout =)
Wayne Haffenden