Just for testing and didactical porpouse not strictly bound to this question I was writing a five minute console application to emit an event to RabbitMQ using Rebus. My target is only to do a Publish of an event, but I have some problems registering the bus with WindsorCastle. At my work we have several Windows Service Jobs that do that in sofisticated way, now at home a get problems with somethig appearing to me simple, but I guess I miss some basics of IOC maybe...
This is Program.cs, in which I use ask WindsorCastle to set the container for IOC:
using Castle.Windsor;
using Serilog;
using System;
namespace RebusRabbitEmitEvent
{
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.ColoredConsole(outputTemplate: "{Timestamp:HH:mm:ss} {Message}{NewLine}{Exception}")
.CreateLogger();
using (var container = new WindsorContainer())
{
container
.Install(new Installer());
EventEmitter emitter = container.Resolve<EventEmitter>();
emitter.Emit();
Console.WriteLine("Press ENTER to quit");
Console.ReadLine();
}
}
}
}
Here is Installer.cs in which I set all DI:
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using Rebus.Activation;
using Rebus.Bus;
using Rebus.Config;
using Rebus.Retry.Simple;
namespace RebusRabbitEmitEvent
{
public class Installer : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IBus>()
.UsingFactoryMethod(k =>
{
var cnstring = "amqp://guest:guest@localhost:5672";
var inputQueueName = "Pippo.Input";
var errorQueueName = "Pippo.Error";
return Configure.With(k.Resolve<IHandlerActivator>())
.Logging(l => l.ColoredConsole())
.Transport(t => t.UseRabbitMq(cnstring, inputQueueName))
.Routing(r => r.TypeBasedRoutingFromAppConfig())
.Options(o => o.SimpleRetryStrategy(errorQueueAddress: errorQueueName, maxDeliveryAttempts: 2))
.Options(o => o.SetNumberOfWorkers(1))
.Options(o => o.SetMaxParallelism(1))
.Start();
}).LifestyleSingleton(),
Component.For<IHandlerActivator>()
.UsingFactoryMethod(k => new CastleWindsorContainerAdapter(container)),
Component.For<EventEmitter>()
.UsingFactoryMethod(k =>
{
return new EventEmitter(k.Resolve<IBus>());
})
);
}
}
}
Here is EventEmitter.cs in which I publish an event:
using Rebus.Bus;
using RebusRabbitEmitEvent.Events;
using System;
public class EventEmitter
{
private IBus _bus = null;
public EventEmitter(IBus bus)
{
_bus = bus;
}
public void Emit()
{
_bus.Publish(new OperationDoneEvent(Guid.NewGuid(), 0, "Valore del parametro")).Wait();
}
}
Here the event code:
using System;
namespace RebusRabbitEmitEvent.Events
{
public interface IEvent
{
Guid Id { get; }
}
[Serializable]
public class Event : IEvent
{
public Guid Id { get; private set; }
public int Version;
public Event(Guid id, int version)
{
this.Id = id;
this.Version = version;
}
}
public class OperationDoneEvent : Event
{
public string ParameterPassedThroughEvent { get; protected set; }
public OperationDoneEvent(Guid id, int version, string parameterPassedThroughEvent) : base(id, version)
{
ParameterPassedThroughEvent = parameterPassedThroughEvent;
}
}
}
When I run the program I get an exception on Installer.cs during return Configure.With(k.Resolve()) , the exceptio says: "System.InvalidOperationException: 'An IBus service is already registered in this container. If you want to host multiple Rebus instances in a single process, please use separate container instances for them.'"
Any other Dependency Injection Object I test works great. + If I don't use WindsorCastle and set the bus directly in EventEmitter.cs, the program works well.
Is there What's wrong? What I misunderstand?
UPDATE:
I downloaded source code of Rebus.CastleWindsor. I see that with my code when hit CastleWindsorContainerAdapter, windsorContainer owns already an IBus:
I tried to change a little the installer, remove the factory method of IBus and creating the IBus directly in the factory method of EventEmitter, like this:
Component.For<EventEmitter>()
.UsingFactoryMethod(k =>
{
var cnstring = "amqp://guest:guest@localhost:5672";
var inputQueueName = "Pippo.Input";
var errorQueueName = "Pippo.Error";
IBus bus = Configure.With(new CastleWindsorContainerAdapter(container))
.Logging(l => l.ColoredConsole())
.Transport(t => t.UseRabbitMq(cnstring, inputQueueName))
.Routing(r => r.TypeBasedRoutingFromAppConfig())
.Options(o => o.SimpleRetryStrategy(errorQueueAddress: errorQueueName, maxDeliveryAttempts: 2))
.Options(o => o.SetNumberOfWorkers(1))
.Options(o => o.SetMaxParallelism(1))
.Start();
return new EventEmitter(bus);
})
Now in ctor of CastleWindsorContainerAdapter, in windsorContainer there is no more IBus:
With this change the SetBus method of CastleWindsorContainerAdapter does not throw the exception and the code works.
A this point I am a little confused, I thought the original was in right way,I guessed that in "return new EventEmitter(k.Resolve());" the resolve would use the factory method of IBus, I saw many Windows Service with similar Installer, I repeat maybe I miss some basics of IOC, or maybe is something specific with this situation..?
UPDATE 2:
As stated from mookid8000 in his comment of his answer, the IBUS has not to be regestered in the container, indeed so it is in his answer (and example in documentation). I post the full correct Installer for clarity:
namespace RebusRabbitEmitEvent
{
public class Installer : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
Configure.With(new CastleWindsorContainerAdapter(container))
.Logging(l => l.ColoredConsole())
.Transport(t => t.UseRabbitMq("amqp://guest:guest@localhost:5672", "Pippo.Input"))
.Routing(r => r.TypeBasedRoutingFromAppConfig())
.Options(o => o.SimpleRetryStrategy(errorQueueAddress: "Pippo.Error", maxDeliveryAttempts: 2))
.Options(o => o.SetNumberOfWorkers(1))
.Options(o => o.SetMaxParallelism(1))
.Start();
container.Register(
Component.For<EventEmitter>()
.UsingFactoryMethod(k =>
{
return new EventEmitter(k.Resolve<IBus>());
})
);
}
}
The error is simply that you don't let Rebus' Castle Windsor integration register itself in the container.
The correct way to configure Rebus with Windsor is to do it like this:
Configure.With(new CastleWindsorContainerAdapter(container))
.(...)
.Start();
e.g. from an installer like this:
public class RebusInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
Configure.With(new CastleWindsorContainerAdapter(container))
.(...)
.Start();
}
}
I.e.: Don't register IBus
(or ISyncBus
or IMessageContext
for that matter) in the container yourself, because Rebus will do that when you use one of its container adapters.
Check out the readme for some more explanation.