2010-03-09 7 views
4

J'ai essayé de faire fonctionner nServiceBus avec Ninject 2.0 comme le conteneur IoC sous-jacent sans succès. Bien que je puisse réaliser une intégration de base, j'ai eu des problèmes avec les messages "fantômes" envoyés aux différents abonnés. J'ai utilisé l'implémentation Autofac comme un modèle, en remplaçant les pièces nécessaires par du code spécifique à Ninject. De plus, j'ai dû créer une heuristique personnalisée pour que l'injection de propriété automatique se produise. Quoi qu'il en soit, le comportement que je vois est qu'un premier message peut être publié et lu avec succès par un abonné; cependant, le message suivant qui est publié entraîne la réception du message trois fois. Donc, je me demande: Quelqu'un fait quelque chose avec Ninject en tant que ObjectBuilder nServiceBus? Ou est-ce que quelqu'un a vu et corrigé ce comportement lors de l'intégration des autres conteneurs IoC actuellement fournis avec nServiceBus 2.0 (à savoir Windsor, StructureMap ou Autofac).Toute personne utilisant Ninject 2.0 comme ObjectBuilder nServiceBus?

Editer: J'ai jeté un coup d'oeil à this mais il n'a pas l'air complet et je pensais que l'heuristique pour l'injection de propriété devrait être un peu différente.

Répondre

4

trouvé la solution, bien que j'avais deux problèmes.

Le premier problème provient de la manière dont l'objet a été enregistré/configuré avec le noyau Ninject dans la méthode IContainer.Configure de ma classe NinjectObjectBuilder. Après avoir examiné les implémentations existantes de ObjectBuilder de nServiceBus en utilisant d'autres conteneurs IoC, j'ai noté que l'approche générale de l'enregistrement consistait à enregistrer le type concret lui-même ainsi que toutes les interfaces du type implémenté. Dans Ninject, cela revient à "lier le type concret à lui-même" puis à lier chaque interface que ce type implémente au type. C'était assez simple, sauf ce que je trouvais après le profilage avec dotTrace était que, dans le cas des activations de Singleton, il ne semblait pas que je recevais vraiment des références Singleton. En fait, ce qui arriverait, c'est que j'obtiendrais un nouvel objet en fonction du type de service demandé. Par exemple, le type de béton UnicastBus implémente IBus ainsi que IStartableBus et est enregistré avec la portée singleton. nServiceBus s'attend à recevoir le même objet si un IBus ou un IStartableBus est requis, s'ils sont des singletons et s'ils sont tous deux "liés" à la même implémentation. L'interprétation de singleton de singleton semble être par rapport au service ou à l'interface - en d'autres termes, vous obtenez la même instance d'un UnicastBus chaque fois que vous demandez un IBus; Toutefois, vous recevez une nouvelle, différente UnicastBus pour une demande de IStartableBus. La façon dont je résolu ce problème était de mettre en œuvre la méthode IContainer.Configure comme suit:

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

qui a résolu la question de la résolution des cas singleton d'une manière conforme aux attentes de nServiceBus. Cependant, j'ai toujours eu un problème de réception de messages "fantômes" dans mes gestionnaires. Après le peignage des fichiers journaux log4net, le profilage et enfin la lecture du problème comme discuté here et here. Le problème provient spécifiquement du fait que des gestionnaires d'événements multiples sont attachés pendant l'injection de propriété. Plus précisément, le problème est dû au fait que la propriété Transport d'UnicastBus a été définie plusieurs fois. Voici l'extrait de code d'UnicastBus.cs dans la base de code nServiceBus:

public virtual ITransport Transport 
{ 
    set 
    { 
    transport = value; 

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

}

Après réflexion, je me demandais pourquoi cette propriété était mis à plusieurs reprises. UnicastBus est enregistré dans la portée singleton par nServiceBus, et je venais de résoudre ce problème comme indiqué ci-dessus. Il s'avère que Ninject, lors de l'activation d'un objet - qu'il soit nouveau ou à partir de son cache interne - cherchera toujours à injecter les propriétés de l'objet. Il appellera les classes heuristiques d'injection enregistrées avec son conteneur de composants internes du noyau et, en fonction de leur réponse (c'est-à-dire le résultat de l'appel à leur implémentation bool ShouldInject(MemberInfo member)), injectera les propriétés avant chaque activation. Ainsi, la solution était d'empêcher Ninject d'effectuer une injection de propriété sur des instances qui avaient été précédemment activées et qui étaient des singletons. Ma solution consistait à créer une nouvelle stratégie d'injection de propriété qui gardait trace des instances précédemment activées qui n'étaient pas transitoires dans la portée et ignorer la stratégie d'injection de propriété par défaut pour les demandes d'activation de telles instances. Ma stratégie ressemble à ceci:

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

Ma mise en œuvre fonctionne maintenant. Le seul autre défi que j'avais était de "remplacer" la stratégie d'activation existante pour l'injection de propriété. J'ai pensé à créer un noyau personnalisé (et cela pourrait être la meilleure solution); Cependant, vous n'êtes pas en mesure de supporter un noyau "pré-configuré" pour nServiceBus. Pour l'instant j'ai une méthode d'extension qui ajoute les nouveaux composants à n'importe quel noyau 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>(); 
} 

Ce bidouille un pur et simple à ce moment car il n'y a pas de mécanisme sur le conteneur de composants du noyau pour « remplacer » un composant existant. Et, puisque je voulais contourner le comportement existant de la stratégie d'injection de propriété, je ne pouvais pas avoir plus d'un de ces types spécifiques de stratégies dans le noyau à la fois. L'autre problème avec cette implémentation actuelle est que tous les autres composants IActivationStrategy personnalisés qui ont pu être configurés seront perdus. Je voulais écrire du code qui ramènerait tous les composants IActivationStrategy dans une liste, les retirer du noyau, remplacer la stratégie d'injection de propriété dans la liste que j'ai créée, puis les réintégrer dans le noyau, les remplaçant ainsi efficacement. Cependant, le conteneur de composants du noyau ne supporte que la méthode générique Add et je n'ai pas envie d'écrire le code funky pour créer un appel dynamique.

** EDIT ** Après avoir posté hier, j'ai décidé de mieux gérer la stratégie. Voici ce que je faisais, regrouper tout dans une méthode d'extension pour configurer un Ninject noyau:

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

Il y a un fil de discussion sur le groupe nservicebus, pas encore de solution.

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

+0

Merci Andreas. J'ai lu à travers la discussion et est également tombé sur ce fil aussi: http://tech.groups.yahoo.com/group/nservicebus/message/5977 (semble lié?) Donne-moi une idée de ce que le problème est. Si je trouve quelque chose à partager avec tout le monde. –

1

Hy Peter,

J'ai trouvé une approche d'échange dynamique les stratégies (je le fais dans le constructeur de la 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

J'ai écrit le code pour configurer dynamiquement la stratégie. Après avoir posté hier, j'ai senti que ça en valait la peine et vraiment pas si dur! –

0

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

Cet exemple montre un site Web envoyant des commandes via NServiceBus à une injection de dépendance Backend avec Ninject pour le site Web et le backend.

J'ai copié le NinjectObjectBuilder de the official repository comme étant lié par Daniel Marbach. Pour autant que je sache, le code n'a pas encore été publié dans le cadre de la version grand public de NServiceBus. Je veux l'utiliser aujourd'hui, j'ai donc copié le code et l'ai lié à NServiceBus 2.6.

Questions connexes