2010-03-09 4 views
4

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

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

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

Edit: я взглянуть на this, но это не выглядело полным, и я думал, что эвристика для инъекций собственности должно быть немного по-другому.

ответ

4

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

Первая проблема связана с тем, как объект был зарегистрирован/сконфигурирован с ядром Ninject в методе IContainer.Configure моего класса NinjectObjectBuilder. Изучив существующие реализации nServiceBus's ObjectBuilder с использованием других контейнеров IoC, я отметил, что общий подход к регистрации заключался в регистрации самого конкретного типа, а также всех интерфейсов, которые были реализованы. В Ninject это означает «привязку конкретного типа к себе», а затем привязывает каждый интерфейс, который также реализует тип. Это было довольно просто, за исключением того, что я нашел после профилирования с dotTrace, было то, что в случае активации Singleton не показалось, что я действительно получаю ссылки на Singleton. Фактически, произойдет то, что я получаю новый объект в зависимости от типа обслуживания. Например, бетонный тип UnicastBus реализует IBus, а также IStartableBus и зарегистрирован в области Singleton. nServiceBus ожидает получить тот же объект, если запрашивается IBus или IStartableBus, если они являются одноточечными и обе связаны с одной и той же реализацией. Интерпретация Singleton 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, профилирования и, наконец, чтения вопроса, как обсуждалось here и here. Проблема связана с обработчиками взаимных событий, которые привязаны во время ввода свойств. В частности, проблема возникает из-за того, что UnicastBus имеет значение Transport, установленное mutliple times. Вот фрагмент кода из UnicastBus.CS в кодовой базе nServiceBus:

public virtual ITransport Transport 
{ 
    set 
    { 
    transport = value; 

    transport.StartedMessageProcessing += TransportStartedMessageProcessing; 
    transport.TransportMessageReceived += TransportMessageReceived; 
    transport.FinishedMessageProcessing += TransportFinishedMessageProcessing; 
    transport.FailedMessageProcessing += TransportFailedMessageProcessing; 
    } 

}

Подумав об этом, я задавался вопросом, почему это свойство быть установлено несколько раз. UnicastBus зарегистрирован в области singleton с помощью 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, и мне не хотелось писать фанк-код для создания динамического вызова.

** EDIT ** После того, как я опубликовал вчера, я решил лучше справиться с этой стратегией. Вот что я сделал, связывая все в метод расширения для настройки 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) 
    ); 
} 
5

Существует нить, обсуждающая это в группе nservicebus, но решения пока нет.

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

+0

Спасибо, Андреас. Я прочитал дискуссию, а также наткнулся на эту тему: http://tech.groups.yahoo.com/group/nservicebus/message/5977 (похоже, что-то похожее?) Дает мне некоторое представление о том, в чем проблема. Если я найду что-нибудь, вы поделитесь со всеми. –

1

Hy Питер,

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

Я написал код для динамической настройки стратегии. После того, как я опубликовал вчера, я чувствовал, что это стоит усилий, и на самом деле это было не так сложно! –

0

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

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

Я скопировал NinjectObjectBuilder с the official repository по ссылке Daniel Marbach. Насколько мне известно, код еще не был выпущен как часть основной версии NServiceBus. Я хочу использовать его сегодня, поэтому я скопировал код и связал его с NServiceBus 2.6.