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
- Call the SP periodically from your service and process the reminders
- 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..