Mike Lindegarde... Online

Things I'm likely to forget.

Using WCF to Monitor Your Windows Services

Background...

Skip the story

Having come to age as a professional developer in an era where putting business logic in your database was considered sacrilege, I never used the database for anything more than storing data.  Using SQL Server was (is) a last resort.

A few months back I was working on a project at a company that has its roots firmly planted in a database oriented approach to development.  I get that it's impossible to rewrite a massive legacy system every time contemporary programming practices change.  However, I was surprised how quickly (and frequently) developers turned to the database or logging as a solution.

One such example, we needed a way to monitor and manage a Windows service.  Certainly logging provided a low level means of monitoring; however, it didn't provide an effective way to manage the Windows service.  One suggestion was to use a table in a database as a control mechanism.  That could work, but what about a more direct approach?

Setting up the solution

In this example we'll Create two console applications.  One will use TopShelf to start a Windows service (this post doesn't cover TopShelf).  The other will be a normal console application that'll communicate with the Windows service via WCF.  Generally I prefer to put a WPF application in the system tray; however, I'm keeping it simple for this example.

Create a blank Visual Studio 2015 solution named ServiceMonitorDemo.

The Windows service project

Add a new C# Console Application to your project named ServiceMonitorDemo.Service.  The first thing you'll need to do is to add TopShelf to the project:

Install-Package TopShelf

With that taken care of, you'll need to write two service contracts.  For this tutorial we're going to use a duplex channel.  You'll need one interface for each direction through the channel.

 First write the contract that other programs will use to communicate with the service:

using System.ServiceModel;

namespace ServiceMonitorDemo.Service.Contracts
{
    [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IDemoServiceCallbackChannel))]
    public interface IDemoServiceChannel
    {
        [OperationContract(IsOneWay = true)]
        void Connect();

        [OperationContract(IsOneWay = false)]
        bool DisplayMessage(string message);
    }
}

You'll notice this service contract is decorated with the ServiceContract attribute.  This is how you tell .NET what interface to use for the callback contract.  The callback contract is used by the service to communicate with connected clients.  You'll define the callback interface shortly.

Notice that the contract consists of two methods:

  • Connect - This method is used to add the calling client to our list of connected clients.
  • DisplayMessage - This is used as an example of bidirectional communication and to show how clients can control the service through WCF

The callback contract is pretty straight forward:

using System.ServiceModel;
using ServiceMonitorDemo.Model;

namespace ServiceMonitorDemo.Service.Contracts
{
    [ServiceContract]
    public interface IDemoServiceCallbackChannel
    {
        [OperationContract(IsOneWay = true)]
        void UpdateStatus(StatusUpdate status);

        [OperationContract(IsOneWay = true)]
        void ServiceShutdown();
    }
}

Again, our simple service contract has just two methods

  • UpdateStatus - Used by the service to push the service's status out to all connected clients.
  • ServiceShutdown - WCF does not cleanly handle shutting down things.  We need to make sure that the code takes care of opening and closing connection correctly.

With that out of the way we need to take care of writing the actual service.  For this example the service won't do anything exciting, it'll simply post a status object to all connected clients.  The code for this is pretty long, so I'm only going to post important sections here.  You can find the completed example solution on GitHub.

In order to accept connections to the service you'll need to initialize the named pipe:

_host = new ServiceHost(this);

NetNamedPipeBinding binding = new NetNamedPipeBinding();
binding.ReceiveTimeout = TimeSpan.MaxValue;

_host.AddServiceEndpoint(typeof(IDemoServiceChannel),
    binding,
    new Uri(Uri));

_host.Open();

This code simply creates a new host using the current object for the ServiceHost.  A named pipe binding is is added to the host.  Clients connect to this endpoint via the Connect method on the channel service contract defined above.  Our service implements the Connect method as follows:

public void Connect()
{
    AddCallbackChannel(OperationContext.Current.GetCallbackChannel<IDemoServiceCallbackChannel>());
}

The only other important detail here is that DemoService class is implemented as a singleton and decorated with the following attribute:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

In this case I have a single service running and I want to share data with all connected clients.  With this in mind, using InstanceContextMode.Single makes sense.  You can also use per session and per call context modes.  You can find a good overview of the differences on Code Project.

The rest of the cod for this project is pretty much boilerplate setup and tare down.

The Console Application

Fortunately this entire application is less than 100 lines long.  Again, I'll refer you to the GitHub repository to see the full implementation.  Below you'll find the most important section of the code:

        private void Connect()
        {
            while(!_isConnected)
            {
                try
                {
                    DuplexChannelFactory<IDemoServiceChannel> channelFactory = new DuplexChannelFactory<IDemoServiceChannel>(
                        new InstanceContext(this),
                        new NetNamedPipeBinding(),
                        new EndpointAddress(Uri));

                    _channel = channelFactory.CreateChannel();
                    _channel.Connect();

                    _isConnected = true;
                    Console.WriteLine("Channel connected.");
                }
                catch(Exception)
                {
                    Console.WriteLine("Failed to connect to channel.");
                }

                Thread.Sleep(1000);
            }
        }

I wouldn't recommend taking the above code and dumping into your production code.  It's designed to demonstrate how to establish a connection to the Windows service via a WCF named pipe.

As long as the console application is connected to the service, the service will continue to trigger the UpdateStatus method.  In a real world implementation UpdateStatus would most likely toggle some sort of visual status indicator on a WPF application (e.g. a red / green light).  In tutorial land displaying a message in the console works just fine.

Wrap Up

If you've cloned, forked, or downloaded a zip file of the repository you should be able to run the Windows service as a service by navigating to your binary folder for the project (Debug or Release depending on your active build configuration) in your preferred terminal / command prompt and running:

../ServiceMonitorDemo.Service install
../ServiceMonitorDemo.Service start

You can then run a few instances of the ServiceMonitorDemo.Monitor and see what happens.

GitHub repository Link