私は2つの問題を抱えていましたが、解決策を見つけました。
最初の問題は、オブジェクトが私のクラスのIContainer.Configure
メソッドでNinject カーネルに登録/構成された方法に起因していました。NinjectObjectBuilder
他の IoC コンテナーを使用してnServiceBus の既存の実装を調べた結果、ObjectBuilder
登録の一般的な方法は、具体的な型自体と、その型が実装するすべてのインターフェイスを登録することであることに気付きました。Ninject では、これは「具象型をそれ自体にバインド」し、その型が実装する各インターフェイスもその型にバインドすることになります。dotTraceでプロファイリングした後に見つけたものを除いて、これはかなり簡単でした。シングルトン アクティベーションの場合、実際にシングルトン参照を取得しているようには見えませんでした。実際には、要求されたサービスのタイプに応じて、新しいオブジェクトが取得されます。たとえば、UnicastBus
具象型はシングルトン スコープに実装され、登録されますIBus
。IStartableBus
nServiceBus は、IBus
orIStartableBus
が要求されたかどうかにかかわらず、それらがシングルトンであり、両方が同じ実装に「バインド」されている場合、同じオブジェクトを受け取ることを期待しています。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 ログ ファイルをくまなく調べ、プロファイリングし、最後に問題を読み取った後。この問題は、プロパティ インジェクション中に複数のイベント ハンドラーがアタッチされていることが原因で発生します。具体的には、この問題は、UnicastBus のTransport
プロパティが複数回設定されているために発生します。nServiceBus コード ベースの UnicastBus.cs のコード スニペットを次に示します。
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>();
}
これは、既存のコンポーネントを「置き換える」ためのメカニズムがカーネル コンポーネント コンテナーにないため、現時点では完全なハッキングです。また、プロパティ インジェクション戦略の既存の動作をオーバーライドしたかったため、これらの特定のタイプの戦略を一度に複数カーネルに含めることはできませんでした。この現在の実装のもう 1 つの問題は、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)
);
}