« Back
in Rebus MSMQ ninject events C# read.

Event Listener Windows Service with Rebus and Ninject.

Sometimes we want a central location where all of our applications can send events to and have that location handle the events that supports our system. We also want to make sure that the messages/events don't disappear into the void if one of those applications happen to fall over. With a reliable transport system like MSMQ and Rebus, the messages can sit in the queue until the application fires up again to process it. From Rebus' website:

Rebus is a lean service bus implementation for .NET, similar in nature to NServiceBus and MassTransit, only leaner.

What's Happening

  1. Application sends a message containing <TEvent> to our Windows service's private queue
  2. Windows service picks up the message and processes it with a custom handler

The overview would be something along the lines of a separate application with an event implemented as a POCO cs file. It uses Rebus as well to send this event into the remote service's private queue where the windows service is sitting. The service will then pick up that item from its queue and process it with a custom handler bound by our favourite IoC container.

TestEvent Class

Our event is just a POCO which other projects/applications will need a reference to.

public class TestEvent{}  

The Event Handler

The event handler itself looks like this:

public class TestHandler:IHandleMessages<TestEvent>  
{
    public void Handle(TestEvent @event)
    {
        //Handle the event here
    }
}

Pretty clean and we can inject other dependencies we need in here and have them in a different folder / project altogether for easier maintenance. The class must implement Rebus' IHandle<TEvent> interface in order to get picked up by Rebus.

Windows Service with Ninject

We'll need to create an entry point for Ninject by creating a new console application in Visual Studio. Then we can add a Windows Service item to the project.

The project itself which we'll call Application.Service will need the following Nuget Packages:
1. Install-Package Ninject
2. Install-Package Rebus
3. Install-Package Rebus.Ninject

Optionally if you wanted to add logging you could do it with NLog:
4. Install-Package Rebus.NLog

As we can see here, Rebus has a ton of add-ons and is very flexible.

You will also need to add an App.config to the project to configure Rebus' input queue name.

This is what the Program.cs file looks like to wire up Ninject.

namespace Application.Service  
{
    class Program
    {
        static void Main(string[] args)
        {
            IKernel kernel = new Ninject.StandardKernel();
            kernel.Bind<ILogger>().To<NLogLogger>();
            kernel.Bind<IContainerAdapter>().To<Rebus.Ninject.NinjectContainerAdapter>().WithConstructorArgument("kernel",kernel);
            kernel.Bind<IHandleMessages<TestEvent>>().To<TestHandler>();

            kernel.Bind<EventListenerService>().ToSelf();

            if (System.Diagnostics.Debugger.IsAttached)
            {
                var eventListener = kernel.Get<EventListenerService>();
                eventListener.FakeStart();
                Console.ReadLine();
            }
            else
            {
                var ServicesToRun=new ServiceBase[] { kernel.Get<EventListenerService>() };
                ServiceBase.Run(ServicesToRun);
            }
        }
    }
}

First we need to make an instance of Ninject's IKernel and this is what will fetch our concrete implementations of the services we wish to use. In this case I've made an ILogger service.

Part of Rebus' configuration requires an IContainerAdapter which we'll also put here and pass it onto the service later. This is in case we wish to use another IoC container instead of Ninject down the road.

In order to register the handlers to the events we'll need to bind Rebus' IHandleMessages with our handler.

Finally, if we're debugging, we want to run the service without directly calling its OnStart method. We do this by creating a method called FakeStart in the service which calls OnStart with null parameters as we will see in a moment.

We use the kernel here to fetch our concrete implementation of the service which we've called EventListenerService which happens to be itself. After this initial binding, the rest of the bound interfaces called inside of the EventListenerService will be brought in accordingly.

The EventListener Service

We'll rename our service added earlier to EventListenerService and it'll contain the following code:

namespace Application.Service  
{
    partial class EventListenerService : ServiceBase
    {
        private readonly ILogger _logger;
        private readonly IContainerAdapter _adapter;
        public EventListenerService(ILogger logger, IContainerAdapter adapter)
        {
            _logger = logger;
            _adapter = adapter;
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            var bus = Configure.With(_adapter)
                .Logging(l => l.NLog())
                .Transport(t => t.UseMsmqAndGetInputQueueNameFromAppConfig())
                .MessageOwnership(o => o.FromRebusConfigurationSection())
                .CreateBus()
                .Start();
        }

        protected override void OnStop()
        {
            // TODO: Add code here to perform any tear-down necessary to stop your service.
            _logger.Trace("Service Stopped");
        }

        public void FakeStart()
        {
            OnStart(null);
        }
    }
}

When our service starts, we create an instance of the bus and had it start listening to the private queue. Logging by Rebus has been bound to NLog and we've configured it to use MSMQ from the configuration file we added earlier. If the bus doesn't exist, it'll create one.

All of our services have been pulled in by Ninject and we don't have any of that messy code here. Now Rebus will listen on the queue for TestEvent and handle it with our TestEventHandler class. No need to do any polling or sleeping on the threads.

App.Config on the Windows Service

The App.Config we added earlier will need a configuration section added for Rebus. This is so we have a listen queue name where the Windows service is hosted.

  <configSections>
    <section name="rebus" type="Rebus.Configuration.RebusConfigurationSection, Rebus" />
  </configSections>
  <rebus inputQueue="AppEvent.input" errorQueue="AppEvent.error" />

In this case our queue name is AppEvent. If you want to see the messages in the queue, just open up Computer Management and go to private queue's.

App.Config on the Sender Application

On the application that's sending the message to our service we need to add an EndPoint. That EndPoint will be our Windows service's queue name and machine name as follows:

  <configSections>
    <section name="rebus" type="Rebus.Configuration.RebusConfigurationSection, Rebus" />
  </configSections>

  <rebus inputQueue="AppEventTest.input" errorQueue="AppEventTest.error" >
    <endpoints>
      <add messages="Application.Service" endpoint="[email protected]_PC"/>
    </endpoints>
  </rebus>

Test Program To Send Event

Here is a quick console application that sends off a single TestEvent to our EventListenerService

class Program  
{
    static void Main(string[] args)
    {
        var publisher = new TestPublisher();
        publisher.Send(new TestEvent());

    }
}

public class TestPublisher  
{
    private readonly IBus _bus;
    public TestPublisher()
    {
        var adapter = new BuiltinContainerAdapter();
        var config = Configure.With(adapter)
            .MessageOwnership(t => t.FromRebusConfigurationSection())
            .Transport(t => t.UseMsmqAndGetInputQueueNameFromAppConfig());

        var startable = config.CreateBus();
        _bus = startable.Start();
    }

    public void Send<T>(T message)
    {
        _bus.Send(message);
    }
}
comments powered by Disqus