Found the solution, though I had a two problems.
The first problem stemmed from the manner in which object were registered/configured with the Ninject kernel in the IContainer.Configure
method of my NinjectObjectBuilder
class. Having examined the existing implementations of nServiceBus's ObjectBuilder
using other IoC containers, I noted the general approach to registration was to register the concrete type itself as well as all interfaces the type implemented. In Ninject, this amounts to "binding the concrete type to itself" and then binding each interface that type implements to the type as well. This was fairly straightforward, except what I was finding after profiling with dotTrace was that, in the case of Singleton activations, it didn't appear that I was truly getting Singleton references. In fact, what would happen is that I would get a new object depending on type of service was requested. For example, the UnicastBus
concrete type implements IBus
as well as IStartableBus
and is registered with singleton scope. nServiceBus expects to receive the same object whether an IBus
or IStartableBus
is requested, if they are singletons and both "bound" to the same implementation. Ninject's interpretation of singleton appears to be with respect to the service or interface -- in other words, you get the same instance of a UnicastBus
every time you request an IBus
; however, you receive a new, different UnicastBus
for a request for IStartableBus
. The way I solved this was to implement the IContainer.Configure
method as follows:
void IContainer.Configure(Type concreteComponent,
ComponentCallModelEnum callModel) {
if (HasComponent(concreteComponent))
return;
var services = concreteComponent.GetAllServices()
.Where(t => t != concreteComponent);
var instanceScope = GetInstanceScopeFrom(callModel);
// Bind the concrete type to itself ...
kernel
.Bind(concreteComponent)
.ToSelf()
.InScope(instanceScope);
// Bind "aliases" to the binding for the concrete component
foreach (var service in services)
kernel
.Bind(service)
.ToMethod(ctx => ctx.Kernel.Get(concreteComponent))
.InScope(instanceScope);
}
That solved the issue of resolving singleton instances in a manner consistent with nServiceBus's expectations. However, I still had a problem of receiving "ghost" messages in my handlers. After combing through log4net log files, profiling and finally reading the issue as discussed here and here. The problem specifcially stems from mutliple event handlers being attached during property injection. Specifically, the issue is caused due to the UnicastBus having it's Transport
property set mutliple times. Here's the code snippet from UnicastBus.cs in the nServiceBus code base:
public virtual ITransport Transport
{
set
{
transport = value;
transport.StartedMessageProcessing += TransportStartedMessageProcessing;
transport.TransportMessageReceived += TransportMessageReceived;
transport.FinishedMessageProcessing += TransportFinishedMessageProcessing;
transport.FailedMessageProcessing += TransportFailedMessageProcessing;
}
}
After thinking about it, I wondered why this property was being set multiple times. UnicastBus
is registered in singleton scope by nServiceBus, and I had just fixed that problem as discussed above. Turns out, Ninject, when activating an object -- whether new or from it's internal cache -- will still look to inject the properties of the object. It will call the injection heuristics classes registered with its internal kernel component container and based on their response (i.e. the result of the call to their bool ShouldInject(MemberInfo member)
implementation,) inject the properties prior to each activation. So, the solution was to prevent Ninject from performing property injection on instances that had been previously activated and were singletons. My solution was to create a new property injection strategy that kept track of previously activated instances that were not transient in scope and skip the default property injection strategy for activation requests for such instances. My strategy looks like this:
/// <summary>
/// Only injects properties on an instance if that instance has not
/// been previously activated. This forces property injection to occur
/// only once for instances within a scope -- e.g. singleton or within
/// the same request, etc. Instances are removed on deactivation.
/// </summary>
public class NewActivationPropertyInjectStrategy : PropertyInjectionStrategy {
private readonly HashSet<object> activatedInstances = new HashSet<object>();
public NewActivationPropertyInjectStrategy(IInjectorFactory injectorFactory)
: base(injectorFactory) { }
/// <summary>
/// Injects values into the properties as described by
/// <see cref="T:Ninject.Planning.Directives.PropertyInjectionDirective"/>s
/// contained in the plan.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="reference">A reference to the instance being
/// activated.</param>
public override void Activate(IContext context,
InstanceReference reference) {
if (activatedInstances.Contains(reference.Instance))
return; // "Skip" standard activation as it was already done!
// Keep track of non-transient activations...
// Note: Maybe this should be
// ScopeCallback == StandardScopeCallbacks.Singleton
if (context.Binding.ScopeCallback != StandardScopeCallbacks.Transient)
activatedInstances.Add(reference.Instance);
base.Activate(context, reference);
}
/// <summary>
/// Contributes to the deactivation of the instance in the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="reference">A reference to the instance being
/// deactivated.</param>
public override void Deactivate(IContext context,
InstanceReference reference) {
activatedInstances.Remove(reference.Instance);
base.Deactivate(context, reference);
}
}
My implementation is now working. The only other challenge I had was "replacing" the existing activation strategy for property injection. I thought about creating a custom kernel (and that may be the best way to go); however, you then aren't able to support a "pre-configured" kernel for nServiceBus. For now I have an extension method that adds the new components to any Ninject kernel.
public static void ConfigureForObjectBuilder(this IKernel kernel) {
// Add auto inject heuristic
kernel.Components.Add<IInjectionHeuristic, AutoInjectBoundPropertyTypeHeuristic>();
// Replace property injection activation strategy...
/* NOTE: I don't like this! Thinking about replacing the pipeline component
* in Ninject so that it's smarter and selects our new activation
* property inject strategy for components in the NServiceBus DLLs and
* uses the "regular strategy" for everything else. Also, thinking of
* creating a custom kernel.
*/
kernel.Components.RemoveAll<IActivationStrategy>();
kernel.Components.Add<IActivationStrategy,
NewActivationPropertyInjectStrategy>();
// The rest of the "regular" Ninject strategies ...
kernel.Components.Add<IActivationStrategy, MethodInjectionStrategy>();
kernel.Components.Add<IActivationStrategy, InitializableStrategy>();
kernel.Components.Add<IActivationStrategy, StartableStrategy>();
kernel.Components.Add<IActivationStrategy, BindingActionStrategy>();
kernel.Components.Add<IActivationStrategy, DisposableStrategy>();
}
This an outright hack at this point as there is no mechanism on the kernel component container for "replacing" an existing component. And, since I wanted to override the existing behavior of the property injection strategy, I couldn't have more than one of those specific types of strategies in the kernel at a time. The other problem with this current implementation is that any other custom IActivationStrategy
components that might have been configured will be lost. I wanted to write code that would get all the IActivationStrategy
components in a list, remove them from the kernel, replace the property injection strategy in the list I created and then add them all back into the kernel, thus effectively replacing them. However, the kernel component container only supports the generic Add
method and I didn't feel like writing the funky code to create a dynamic call.
** EDIT **
After I posted yesterday, I decided to handle the the strategy better. Here's what I did, bundling everything in an extension method to configure a Ninject Kernel:
public static void ConfigureForObjectBuilder(this IKernel kernel) {
// Add auto inject heuristic
kernel.Components.Add<IInjectionHeuristic, AutoInjectBoundPropertyTypeHeuristic>();
// Get a list of all existing activation strategy types
// with exception of PropertyInjectionStrategy
var strategies = kernel.Components.GetAll<IActivationStrategy>()
.Where(s => s.GetType() != typeof (PropertyInjectionStrategy))
.Select(s => s.GetType())
.ToList();
// Add the new property injection strategy to list
strategies.Add(typeof (NewActivationPropertyInjectStrategy));
// Remove all activation strategies from the kernel
kernel.Components.RemoveAll<IActivationStrategy>();
// Add the list of strategies
var addMethod = kernel.Components.GetType().GetMethod("Add");
strategies
.ForEach(
t => addMethod
.MakeGenericMethod(typeof (IActivationStrategy), t)
.Invoke(kernel.Components, null)
);
}