Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

C# Generic Dynamic Windows Service using .NET Reflection

4.05/5 (9 votes)
31 Jan 2008CPOL3 min read 1   705  
A technique to create Windows Service apps that's configurable and dynamic by using the .NET Reflection.

Introduction

This article shows a technique to design Windows Service apps with the following goals:

  1. Can be debugged like a "Console" application without the need to invoke the "Installer" program.
  2. The main "Windows Service" code should be reusable and can be rapidly applied to any new "Windows Service" application development.
  3. Separates the "Server Logic" and allows dynamic switching if desired.
  4. The details of the "Windows Service" during installation are configurable via an XML file.
  5. 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:

  1. GDWS.Common - That's where the generic reusable code for the Windows Service sits.
  2. GDWS.ExampleService - This is an example "server" logic that's aimed to be built as a .NET assembly.
  3. GDWS.ExampleServiceProgram - This is an example "Console" app that actually invokes the server logic.
  4. 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:

C#
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.

C#
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.

C#
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:

C#
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);
  // assuming static method, hence no need to pass any instantiated object
  mStart.Invoke(null, null);
  bool stop = false;
  while (!stop)
  {
    ...
    if (k.KeyChar == 'q' || k.KeyChar == 'Q')
    {
      ...
      MethodInfo mStop = threadProcType.GetMethod(STOP_THREAD_PROC);
      // assuming static method, hence no need to pass any instantiated object
      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

XML
<?xml version="1.0" encoding="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

XML
<?xml version="1.0" encoding="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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)