A Windows Service, and configuration editor, all in one ...
I just finished writing a Windows Service for a utility I needed (for a Christmas gift actually!). Even though the first version is only used on one server in my house, I wanted to have a passable configuration editor for it, rather than relying on XML files, the registry editor, etc.
Additionally, I wanted to be able to run the service as a stand-alone EXE for the purposes of debugging (rather than attaching to the service with the debugger). There are a number of solutions to this problem, but here's the approach I took.
This code is from a file, Program.cs in my project:
namespace UploadingService { static class Program { /// <summary> /// The main entry point for the application. /// </summary> static void Main(string[] args) { if (args.Length > 0) { if (args[0].Equals("I",
StringComparison.InvariantCultureIgnoreCase)) { BasicSettingsDialog dlg = new BasicSettingsDialog(); System.Windows.Forms.Application.Run(dlg);return;
}
}
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Uploader()
};
ServiceBase.Run(ServicesToRun);
}
}
}
The important part of the code above is that I added the string array args to the static Main method, and then compared that to a command line flag, the letter "I". If that flag is present on the command line, I create a settings dialog (BasicSettingsDialog), and start the Windows message-pump loop by calling Application.Run with the dialog instance. When that returns, the process exits.
If the flag is not present, the service starts normally. I built my installer to include a shortcut to the service and also include the command line flag (as an argument below).
In the user interface code, I added a button to 'interactively' start the service (for debugging):
_uploader = new Uploader();
_uploader.InteractiveStart(null);
InteractiveStart is an internal method that calls the overridden ServiceBase method OnStart:
internal void InteractiveStart(string[] args)
{
OnStart(args);
}
That, after a little work, calls into a static method in another class in my code:
internal static void Start()
{
EventLog.WriteEntry("WiredPrairie Uploading Service", "WiredPrairie Uploading Service Started.");
s_emailThread = new Thread(new ParameterizedThreadStart(s_Instance.Run));
s_emailThread.IsBackground = true;
s_emailThread.Start(null);
}
All it does is kick off a new thread with a singleton instance of my worker class.
I communicate to the thread (and the actual service) via the Registry, which is polled every minute for up-to-date settings with code like this:
using (RegistryKey software = Registry.LocalMachine.OpenSubKey("SOFTWARE"))
{
using (RegistryKey wp = software.OpenSubKey("WiredPrairie"))
{
using (RegistryKey ups = wp.OpenSubKey("UploadingService"))
{
emailAccountName = (string)ups.GetValue(@"AccountName",
"", RegistryValueOptions.None);
emailPassword = (string)ups.GetValue(@"AccountPassword",
"", RegistryValueOptions.None);
emailPop3Server = (string)ups.GetValue(@"Pop3Server",
"", RegistryValueOptions.None);
}
}
}
Saving settings is easy:
using (RegistryKey software = Registry.LocalMachine.CreateSubKey("SOFTWARE"))
{
using (RegistryKey wp = software.CreateSubKey("WiredPrairie"))
{
using (RegistryKey ups = wp.CreateSubKey("UploadingService"))
{
ups.SetValue(@"AccountName", txtEmailAccountName.Text,
RegistryValueKind.String);
ups.SetValue(@"AccountPassword", txtEmailPassword.Text,
RegistryValueKind.String);
ups.SetValue(@"Pop3Server", txtEmailPop3Server.Text,
RegistryValueKind.String);
}
}
}
By using the registry as a cheap and reliable communication channel, I didn't need to worry about interprocess communication challenges (named pipes, WCF, etc.), creating a custom file format (or XML), file permissions, etc... Yes, there is a concern about registry permissions, but since these settings are intended for use by a service, setting the expectation that my users are administrators isn't out of the question (and right now, the only user is ME!).
If you have questions, post a comment.