SimpleInjectorまたは別のIoCを使用して、SignalRで依存性注入を使用する独自の方法を見つけるのに役立つ、他の回答とともに2セントをここに投入したいと思います。
スティーブンの回答を使用する場合は、ルートを作成する前にハブルートを登録してください。SignalRRouteExtensions.MapHubs
拡張メソッド(別名routes.MapHubs()
)は、ハブルートをマッピングするときにを呼び出すためRegister(Type, Func<object>)
、GlobalHost.DependencyResolver
ルートがマッピングされる前にDefaultDependencyResolver
スティーブンとスワップアウトすると、スティーブンに遭遇します。SimpleInjectorResolver
NotSupportedException
これは私のお気に入りです。なんで?
- より少ないコード
SimpleInjectorDependencyResolver
。
DefaultDependencyResolver
(aka )を置き換える必要はありませんGlobalHost.DependencyResolver
。つまり、コードがさらに少なくなります。
- ルートは、ハブルートをマッピングする前でも後でも作成できます
DefaultDependencyResolver
。これは、を置き換えていないため、「正常に機能」します。
ナタナエルが言ったように、これはクラスへの依存関係を気にする場合にのみ当てはまりますHub
。これはおそらくほとんどの場合に当てはまります。SignalRに他の依存関係を注入することをいじりたい場合は、Stevenの答えを使用することをお勧めします。
Webリクエストごとの依存関係に関する問題Hub
SignalRには興味深い点があります...クライアントがハブから切断すると(たとえば、ブラウザウィンドウを閉じることによって)、Hub
を呼び出すためにクラスの新しいインスタンスが作成されますOnDisconnected()
。これが発生すると、HttpContext.Current
はnullになります。したがって、これHub
にWebリクエストごとに登録されている依存関係がある場合、何かがうまくいかない可能性があります。
Ninjectで
nugetでNinjectとninjectSignalr依存性リゾルバーを使用してSignalR依存性注入を試しました。この構成では、切断イベント中に.InRequestScope()
に注入されると、バインドされた依存関係が一時的に作成されます。はnullなので、Ninjectはそれを無視して、通知せずに一時的なインスタンスを作成することを決定したと思いますHub
。HttpContext.Current
おそらく、これについて警告するようにninjectに指示する構成設定がありましたが、これはデフォルトではありませんでした。
SimpleInjectorで
一方、SimpleInjectorは、にHub
登録されているインスタンスに依存している場合、例外をスローしWebRequestLifestlyle
ます。
タイプNameOfYourHubの登録済みデリゲートが例外をスローしました。タイプNameOfYourPerRequestDependencyの登録済みデリゲートが例外をスローしました。YourProject.Namespace.NameOfYourPerRequestDependencyは「PerWebRequest」として登録されていますが、インスタンスはHttpContextのコンテキスト外で要求されています(HttpContext.Currentはnullです)。このライフスタイルを使用するインスタンスが、アプリケーションの初期化フェーズ中およびバックグラウンドスレッドで実行されているときに解決されないことを確認してください。バックグラウンドスレッドでインスタンスを解決するには、このインスタンスを「Per LifetimeScope」として登録してみてください:https ://simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped 。
...この例外はHttpContext.Current == null
、私が知る限り、SignalRがHub
を呼び出すためにインスタンスを要求したときにのみ発生することに注意してくださいOnDisconnected()
。
Webリクエストごとの依存関係のソリューションHub
これらのどれも本当に理想的ではないことに注意してください、それはすべてあなたのアプリケーション要件に依存します。
Ninjectで
非一時的な依存関係が必要な場合はOnDisconnected()
、クラスの依存関係をオーバーライドしたり、カスタムを実行したりしないでください。その場合、グラフ内の各依存関係は個別の(一時的な)インスタンスになります。
SimpleInjectorで
、、、またはのいずれかとの間のハイブリッドライフスタイルが必要です。がnullでない場合、依存関係は、通常予想されるWebリクエストの間だけ存続します。ただし、がnullの場合、依存関係は一時的に、シングルトンとして、またはライフタイムスコープ内に注入されます。WebRequestLifestlye
Lifestyle.Transient
Lifestyle.Singleton
LifetimeScopeLifestyle
HttpContext.Current
HttpContext.Current
var lifestyle = Lifestyle.CreateHybrid(
lifestyleSelector: () => HttpContext.Current != null,
trueLifestyle: new WebRequestLifestyle(),
falseLifestyle: Lifestyle.Transient // this is what ninject does
//falseLifestyle: Lifestyle.Singleton
//falseLifestyle: new LifetimeScopeLifestyle()
);
詳細についてLifetimeScopeLifestyle
私の場合、EntityFrameworkのDbContext
依存関係があります。これらは、一時的にまたはシングルトンとして登録されたときに問題を引き起こす可能性があるため、注意が必要な場合があります。一時的に登録すると、2つ以上のDbContext
インスタンスに接続されているエンティティを操作しようとしているときに、例外が発生する可能性があります。シングルトンとして登録すると、より一般的な例外が発生します(シングルトンとして登録しないでくださいDbContext
)。私の場合DbContext
、同じインスタンスを多くのネストされた操作で再利用できる特定のライフタイム内に存在する必要がありました。つまり、が必要でしたLifetimeScopeLifestyle
。
上記のハイブリッドコードをこのfalseLifestyle: new LifetimeScopeLifestyle()
行で使用した場合、カスタムIHubActivator.Create
メソッドの実行時に別の例外が発生します。
タイプNameOfYourHubの登録済みデリゲートが例外をスローしました。NameOfYourLifetimeScopeDependencyは「LifetimeScope」として登録されていますが、インスタンスはライフタイムスコープのコンテキスト外で要求されています。必ず最初にcontainer.BeginLifetimeScope()を呼び出してください。
ライフタイムスコープの依存関係を設定する方法は次のようになります。
using (simpleInjectorContainer.BeginLifetimeScope())
{
// resolve solve dependencies here
}
ライフタイムスコープに登録されている依存関係は、このusing
ブロック内で解決する必要があります。さらに、これらの依存関係のいずれかが実装されている場合、それらはブロックIDisposable
の最後で破棄されます。using
次のようなことをしたくはありません。
public IHub Create(HubDescriptor descriptor)
{
if (HttpContext.Current == null)
_container.BeginLifetimeScope();
return _container.GetInstance(descriptor.HubType) as IHub;
}
私はスティーブン(あなたが知らなかった場合に備えてSimpleInjectorの作者でもある)にこれについて尋ねたところ、彼は次のように述べました。
ええと..LifetimeScopeを破棄しないと、大きな問題が発生するので、必ず破棄してください。スコープを破棄しないと、ASP.NETで永遠にぶらぶらします。これは、スコープをネストして親スコープを参照できるためです。したがって、スレッドは最も内側のスコープ(キャッシュを含む)を存続させ、このスコープは親スコープ(キャッシュを含む)を存続させます。ASP.NETはスレッドをプールし、プールからスレッドを取得するときにすべての値をリセットしないため、すべてのスコープが有効なままであり、次にプールからスレッドを取得して新しい有効期間スコープを開始するときに、単純に新しいネストされたスコープを作成すると、これは積み重なっていきます。遅かれ早かれ、OutOfMemoryExceptionが発生します。
IHubActivator
依存関係のスコープを設定するために使用することはできません。これは、依存関係Hub
が作成するインスタンスほど存続しないためです。BeginLifetimeScope()
したがって、メソッドをブロックでラップした場合でも、依存関係はインスタンスが作成さusing
れた直後に破棄されます。Hub
ここで本当に必要なのは、間接参照の別のレイヤーです。
スティーブンの助けのおかげで、私が最終的に得たのは、コマンドデコレータ(およびクエリデコレータ)です。AHub
は、Web要求ごとのインスタンス自体に依存することはできませんが、代わりに、実装が要求ごとのインスタンスに依存する別のインターフェースに依存する必要があります。コンストラクターに注入される実装Hub
は、ライフタイムスコープを開始して破棄するラッパーで(simpleinjectorを介して)装飾されます。
public class CommandLifetimeScopeDecorator<TCommand> : ICommandHandler<TCommand>
{
private readonly Func<ICommandHandler<TCommand>> _handlerFactory;
private readonly Container _container;
public CommandLifetimeScopeDecorator(
Func<ICommandHandler<TCommand>> handlerFactory, Container container)
{
_handlerFactory = handlerFactory;
_container = container;
}
[DebuggerStepThrough]
public void Handle(TCommand command)
{
using (_container.BeginLifetimeScope())
{
var handler = _handlerFactory(); // resolve scoped dependencies
handler.Handle(command);
}
}
}
ICommandHandler<T>
... Webリクエストごとのインスタンスに依存するのは装飾されたインスタンスです。使用されるパターンの詳細については、これとこれをお読みください。
登録例
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), assemblies);
container.RegisterSingleDecorator(
typeof(ICommandHandler<>),
typeof(CommandLifetimeScopeDecorator<>)
);