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