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.

4

5 回答 5

5

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

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

于 2010-03-09T12:51:33.807 回答
4

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)
    );
}
于 2010-03-09T15:21:40.757 回答
3

Pulled my ideas into official repository:

https://github.com/NServiceBus/NServiceBus/tree/master/src/impl/ObjectBuilder/ObjectBuilder.Ninject

Daniel

于 2010-03-10T09:58:16.187 回答
1

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);
于 2010-03-10T08:18:23.120 回答
0

Here's an example of how to use the NinjectObjectBuilder.

This example shows a Website sending Commands through NServiceBus to a Backend with Ninject dependency injection for both the Website and the Backend.

I copied the NinjectObjectBuilder from the official repository as linked by Daniel Marbach. As far as I know the code hasn't been released as part of the mainstream version of NServiceBus yet. I want to use it today so I've copied the code and linked it against NServiceBus 2.6.

于 2012-01-13T22:16:47.820 回答