views:

499

answers:

4

I have been trying to get nServiceBus to work with Ninject 2.0 as the underlying IoC container unsuccessfully. While I can achieve basic integration, I've had issues with "ghost" messages getting sent to the various subscribers. I used the Autofac implementation as a template of sorts, replacing the necessary pieces with Ninject-specific code. Further, I did have to create a custom heuristic to get auto-property injection to occur.

Regardless, the behavior I see is that a first message can be published and successfully read by a subscriber; however the next message that gets published results in the message being "received" three times.

So, I am wondering: Is anyone doing anything with Ninject as the nServiceBus ObjectBuilder? Or, has anyone seen and corrected this behavior during integration of the other IoC containers currently bundled with nServiceBus 2.0 (i.e. Windsor, StructureMap or Autofac).

Edit: I did take a look at this but it didn't look complete and I thought the heuristic for property injection should be a bit different.

+3  A: 

There is a thread discussing this on the nservicebus group, no solution yet though.

http://tech.groups.yahoo.com/group/nservicebus/message/6253

Andreas
Thanks Andreas. I read through the discussion and also came across this thread as well: http://tech.groups.yahoo.com/group/nservicebus/message/5977 (seems related?) Gives me some idea as to what the issue is. If I find anything out will share with everyone.
Peter Meyer
+1  A: 

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)
    );
}
Peter Meyer
A: 

Hy Peter,

I found an approach to dynamically exchange the strategies (I do this in the constructor of the NinjectObjectBuilder):

            this.kernel.Bind<NewActivationPropertyInjectStrategy>().ToSelf().WithPropertyValue("Settings", ctx => ctx.Kernel.Settings);
        this.kernel.Bind<IInjectorFactory>().ToMethod(ctx => ctx.Kernel.Components.Get<IInjectorFactory>());

        this.kernel.Components.Get<ISelector>().InjectionHeuristics.Add(this.propertyHeuristic);

        IList<IActivationStrategy> activationStrategies = this.kernel.Components.Get<IPipeline>().Strategies;

        IList<IActivationStrategy> copiedStrategies = new List<IActivationStrategy>(
            activationStrategies.Where(strategy => !strategy.GetType().Equals(typeof(PropertyInjectionStrategy)))
            .Union(new List<IActivationStrategy> { this.kernel.Get<NewActivationPropertyInjectStrategy>() }));

        activationStrategies.Clear();
        copiedStrategies.ToList().ForEach(activationStrategies.Add);
Daniel Marbach
I wrote the code to dynamically configure the strategy as well. After I posted yesterday, I felt it was worth the effort and really wasn't that hard!
Peter Meyer
A: 

Here is my complete implementation with input from peter which seems to work:

https://gist.github.com/326321/dd17a73ff2386743419b144851a5324bde2a8971

Daniel

Daniel Marbach
I'll take a look at what you did and compare with mine.
Peter Meyer
Had a chance to look at?
Daniel Marbach
Hy Peter,Could you please provide the whole code? Daniel
Daniel Marbach
I like the fact that my solution doesn't rely on reflection ;)
Daniel Marbach