Introduction
This article shows a technique to design Windows Service apps with the following goals:
- Can be debugged like a "Console" application without the need to invoke the "Installer" program.
- The main "Windows Service" code should be reusable and can be rapidly applied to any new "Windows Service" application development.
- Separates the "Server Logic" and allows dynamic switching if desired.
- The details of the "Windows Service" during installation are configurable via an XML file.
- The loading of the server logic is configurable via an XML file.
The sample code I presented here is the basic framework and has been tested with Visual Studio 2008 and .NET 3.5 Framework. But the concept should be applicable to other versions as well.
Background
If you have never created a Windows Service application, there is a "walkthrough" article on the MSDN library site. Obviously, there're many useful articles on The Code Project as well.
Using the Code
By using the presented technique, to create a Windows Service app will just require you to create a new "Console" project using the Visual Studio.
The sample Visual Studio solution consists of 4 projects. They are:
GDWS.Common
- That's where the generic reusable code for the Windows Service sits. GDWS.ExampleService
- This is an example "server" logic that's aimed to be built as a .NET assembly. GDWS.ExampleServiceProgram
- This is an example "Console" app that actually invokes the server logic. SetupExampleService
- This is a typical "Setup & Deployment" project to demonstrate that this technique also works seamlessly with the standard setup mechanism.
In a nutshell, the Main()
program is as simple as this:
namespace GDWS.ExampleServiceProgram
{
class Program
{
static void Main(string[] args)
{
ServiceMainProgram.ServiceMain(args);
}
}
[RunInstaller(true)]
public class ExampleServiceInstaller : CustomServiceInstaller
{
}
}
Class - CustomServiceInstaller
This class mainly reads the ServiceInstall.xml file in order to tell Windows Installer the "Service Name", the "Service Display Name", and the "Service Descriptions".
Class - ServiceMainProgram
In order to load the "server logic" dynamically, .NET Reflection is used to load the DLL that encapsulates the server logic. The two constructors are private
, and you use the two static
methods to invoke the process.
public class ServiceMainProgram
{
...
...
public static void Service(string[] args, Type type)...
public static void Service(string[] args)...
...
}
The static
method that takes two arguments requires you to submit a Type
, so that during creation of an object internally it can use Reflection to find out that the two required static
methods StartThreadProc
and StopThreadProc
are present.
The second static
method that takes only one argument reads the name of the Type
from the ServiceConfig.xml file, which also tells it where to find the server logic assembly DLL. Once the path of the DLL is located, the assembly is loaded by using Reflection.
private Type LoadAssemby(string configFileName)
{
...
...
Assembly asm = Assembly.LoadFile(Path.GetFullPath(assemblyFullPath));
...
Type type = asm.GetType(typeName);
...
}
Once the Type
is found and checked, the following code can utilize the reflected methods:
private void Run()
{
if (debugMode) RunDebug();
else
{
ServiceBase[] servicesToRun = new ServiceBase[]
{ new GenericService(threadProcType) };
ServiceBase.Run(servicesToRun);
}
}
private void RunDebug()
{
...
...
MethodInfo mStart = threadProcType.GetMethod(START_THREAD_PROC);
mStart.Invoke(null, null);
bool stop = false;
while (!stop)
{
...
if (k.KeyChar == 'q' || k.KeyChar == 'Q')
{
...
MethodInfo mStop = threadProcType.GetMethod(STOP_THREAD_PROC);
mStop.Invoke(null, null);
stop = true;
}
...
}
}
Debugging
For debugging purposes, define a command line argument debug
or under the Command Prompt, just type yourservice.exe debug
to invoke the program into debug mode.
Installing as a Proper Windows Service
You can install the sample service app using the bundled Setup project.
To quickly test the app as a Windows Service, you can use the installutil.exe
and uninstall it by calling installutil.exe /u
.
ServiceConfig.xml
="1.0"="utf-8"
<ConfigService>
<ServiceName value="ExampleService"/>
<ServiceDisplayName value="An Example Service"/>
<ServiceDescription value="An example service
that demonstrates a generic and dynamic technique."/>
</ConfigService>
ServiceInstall.xml
="1.0"="utf-8"
<InstallService>
<ServiceAssemblyFullPath value=
"..\..\..\GDWS.ExampleService\bin\Debug\GDWS.ExampleService.dll"/>
<TypeName value="GDWS.ExampleService.ThreadProcExample"/>
</InstallService>
Please note that, although the ServiceAssemblyFullPath
is specified as a relative path, the code will try to locate it locally if it cannot find it at the first instance.
History
- 2008-01-22: Article created