有人将Ninject 2.0用作nServiceBus ObjectBuilder吗? [英] Anyone using Ninject 2.0 as the nServiceBus ObjectBuilder?

查看:81
本文介绍了有人将Ninject 2.0用作nServiceBus ObjectBuilder吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试使nServiceBus与Ninject 2.0作为基础IoC容器一起使用失败.虽然我可以实现基本的集成,但是在将重影"消息发送到各个订户时遇到了问题.我使用Autofac实现作为模板,用Ninject特定的代码替换了必要的部分.此外,我确实必须创建自定义试探法以使自动属性注入发生.

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.

所以,我想知道: 是否有人将Ninject作为nServiceBus ObjectBuilder做任何事情?或者,是否有人在集成当前与nServiceBus 2.0捆绑在一起的其他IoC容器(即Windsor,StructureMap或Autofac)的过程中看到并纠正了此行为.

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).

我确实看过但它看起来并不完整,我认为属性注入的启发式方法应该有所不同.

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.

推荐答案

找到了解决方案,尽管我有两个问题.

Found the solution, though I had a two problems.

第一个问题源于在我的NinjectObjectBuilder类的IContainer.Configure方法中使用Ninject内核注册/配置对象的方式.在检查了使用其他IoC容器的nServiceBus ObjectBuilder的现有实现之后,我注意到注册的一般方法是注册具体类型本身以及所实现类型的所有接口.在Ninject中,这相当于将具体类型绑定到自身",然后将类型实现的每个接口也绑定到该类型.这非常简单,除了在使用 dotTrace 进行分析后发现的是,激活Singleton,似乎并没有真正获得Singleton引用.实际上,将会发生的情况是,我将根据请求的服务类型获得一个新对象.例如,UnicastBus具体类型同时实现IBusIStartableBus并在单例范围内注册.如果nServiceBus是单例并且都绑定"到同一实现,则无论请求IBus还是IStartableBus,nServiceBus都希望接收相同的对象. Ninject对单例的解释似乎是针对服务或接口的-换句话说,每次请求IBus时,都会得到UnicastBus的相同实例;但是,对于IStartableBus的请求,您会收到一个新的不同的UnicastBus.我解决此问题的方法是实现IContainer.Configure方法,如下所示:

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);
}

这解决了以符合nServiceBus期望的方式解决单例实例的问题.但是,我仍然有在处理程序中接收重影"消息的问题.梳理完log4net日志文件后,按概要分析并最终阅读问题,如此处此处.该问题特别是由于在属性注入期间附加了多个事件处理程序而引起的.具体来说,此问题是由于UnicastBus的Transport属性设置了多个时间而引起的.这是nServiceBus代码库中UnicastBus.cs的代码片段:

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;
  }

}

考虑之后,我想知道为什么要多次设置此属性. UnicastBus是由nServiceBus在单例范围内注册的,如上所述,我已经解决了该问题.事实证明,Ninject在激活对象时(无论是新建对象还是来自其内部缓存的对象)仍将看起来是在注入对象的属性.它将调用在其内部内核组件容器中注册并根据其响应(即调用其bool ShouldInject(MemberInfo member)实现的结果)注册的注入启发式类,并在每次激活之前注入属性.因此,解决方案是防止Ninject对先前已激活且为单例的实例执行属性注入.我的解决方案是创建一个新的属性注入策略,该策略可以跟踪先前激活的实例,这些实例在范围上不是暂时的,并跳过针对此类实例的激活请求的默认属性注入策略.我的策略如下:

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);
  }
}

我的实现现在可以正常工作了.我唯一遇到的其他挑战是取代"现有的用于资产注入的激活策略.我考虑过创建一个自定义内核(这可能是最好的方法).但是,您将无法为nServiceBus支持预配置"内核.现在,我有一个扩展方法,可以将新组件添加到任何Ninject内核中.

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>();
}

这是一个彻底的黑客,因为内核组件容器上没有用于替换"现有组件的机制.而且,由于我想覆盖属性注入策略的现有行为,因此一次只能在内核中使用这些特定类型的策略中的一种以上.当前实现的另一个问题是,可能已经配置的所有其他自定义IActivationStrategy组件都将丢失.我想编写将所有IActivationStrategy组件都放在列表中的代码,将其从内核中删除,替换我创建的列表中的属性注入策略,然后将它们全部重新添加到内核中,从而有效地替换它们.但是,内核组件容器仅支持通用的Add方法,我不喜欢编写时髦的代码来创建动态调用.

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.

**编辑** 昨天发布后,我决定更好地处理该策略.这就是我所做的,将所有内容捆绑在扩展方法中以配置Ninject内核:

** 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)
    );
}

这篇关于有人将Ninject 2.0用作nServiceBus ObjectBuilder吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆