Кто-нибудь использует Ninject 2.0 в качестве nServiceBus ObjectBuilder? - PullRequest
4 голосов
/ 09 марта 2010

Я пытался заставить nServiceBus работать с Ninject 2.0 в качестве основного контейнера IoC, но безуспешно. Несмотря на то, что я могу добиться базовой интеграции, у меня были проблемы с «призрачными» сообщениями, отправляемыми различным подписчикам. Я использовал реализацию Autofac в качестве своего рода шаблона, заменив необходимые части кодом, специфичным для Ninject. Кроме того, мне пришлось создать собственную эвристику, чтобы получить автоматическое внедрение свойства.

Несмотря на это, я вижу, что первое сообщение может быть опубликовано и успешно прочитано подписчиком; однако следующее сообщение, которое получает опубликованное, приводит к тому, что сообщение «получено» три раза.

Итак, мне интересно: Кто-нибудь делает что-нибудь с Ninject как nServiceBus ObjectBuilder? Или кто-нибудь видел и исправлял это поведение во время интеграции других контейнеров IoC, в настоящее время связанных с nServiceBus 2.0 (т.е. Windsor, StructureMap или Autofac).

Редактировать: Я взглянул на это , но оно не выглядело завершенным, и я подумал, что эвристика для внедрения свойства должна быть немного другой.

Ответы [ 5 ]

5 голосов
/ 09 марта 2010

В группе nservicebus обсуждается эта тема, но пока нет решения.

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

4 голосов
/ 09 марта 2010

Нашел решение, хотя у меня было две проблемы.

Первая проблема возникла из-за способа, которым объект был зарегистрирован / настроен с помощью ядра Ninject в методе IContainer.Configure моего класса NinjectObjectBuilder. Изучив существующие реализации nServiceBus ObjectBuilder с использованием других контейнеров IoC, я отметил, что общий подход к регистрации заключался в регистрации самого конкретного типа, а также всех интерфейсов реализуемого типа. В Ninject это означает «привязать конкретный тип к себе», а затем привязать каждый интерфейс, который этот тип также реализует к типу. Это было довольно просто, за исключением того, что после профилирования с помощью dotTrace я обнаружил, что в случае синглтон-активаций не похоже, что я действительно получаю ссылки на Синглтон. Фактически, то, что случилось бы, - то, что я получил бы новый объект в зависимости от типа запрошенной услуги. Например, конкретный тип UnicastBus реализует IBus, а также IStartableBus и зарегистрирован с одноэлементной областью действия. nServiceBus ожидает получения одного и того же объекта, независимо от того, запрашивается ли IBus или IStartableBus, если они являются синглетонами и оба «связаны» с одной и той же реализацией. Ninject интерпретирует синглтон, по-видимому, в отношении сервиса или интерфейса - другими словами, вы получаете один и тот же экземпляр UnicastBus каждый раз, когда запрашиваете IBus; тем не менее, вы получите новый, другой UnicastBus для запроса на IStartableBus. Я решил это, реализовав метод IContainer.Configure следующим образом:

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, профилирования и, наконец, чтения проблемы, как обсуждалось здесь и здесь . Проблема, в частности, связана с тем, что обработчики событий Mutliple подключаются во время внедрения свойства. В частности, проблема вызвана тем, что UnicastBus имеет его свойство Transport, установленное несколько раз. Вот фрагмент кода из UnicastBus.cs в кодовой базе nServiceBus:

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 выполнять инъекцию свойств в ранее активированных экземплярах, которые были одиночными. Мое решение состояло в том, чтобы создать новую стратегию внедрения свойств, которая отслеживала ранее активированные экземпляры, которые не были переходными в области действия, и пропускала стратегию внедрения свойств по умолчанию для запросов активации для таких экземпляров. Моя стратегия выглядит так:

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

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, и мне не хотелось писать какой-то забавный код для создания динамического вызова.

** РЕДАКТИРОВАТЬ ** После того, как я опубликовал вчера, я решил лучше справиться со стратегией. Вот что я сделал, связав все в методе расширения для настройки ядра Ninject:

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)
    );
}
3 голосов
/ 10 марта 2010

Вытащил мои идеи в официальный репозиторий:

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

Daniel

1 голос
/ 10 марта 2010

Хи Питер,

Я нашел подход для динамического обмена стратегиями (я делаю это в конструкторе 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);
0 голосов
/ 14 января 2012

Вот пример того, как использовать NinjectObjectBuilder .

В этом примере показано, как веб-сайт отправляет команды через NServiceBus в бэкэнд с внедрением зависимости Ninject для веб-сайта и бэкенда.

Я скопировал NinjectObjectBuilder из официального репозитория , связанного с Дэниелом Марбахом. Насколько я знаю, код еще не был выпущен как часть основной версии NServiceBus. Я хочу использовать его сегодня, поэтому скопировал код и связал его с NServiceBus 2.6.

...