自分のIoCを使用することは、SignalRを使用するのは非常に簡単だと思いましたが、おそらくそうです。おそらく私は何か間違ったことをしている。これが私がこれまでに持っている私のコードです:
private static void InitializeContainer(Container container)
{
container.Register<IMongoHelper<UserDocument>, MongoHelper<UserDocument>>();
// ... registrations like about and then:
var resolver = new SimpleInjectorResolver(container);
GlobalHost.DependencyResolver = resolver;
}
そして私のクラス:
public class SimpleInjectorResolver : DefaultDependencyResolver
{
private Container _container;
public SimpleInjectorResolver(Container container)
{
_container = container;
}
public override object GetService(Type serviceType)
{
return _container.GetInstance(serviceType) ?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _container.GetAllInstances(serviceType) ?? base.GetServices(serviceType);
}
}
最終的には、IJavaScriptProxyGeneratorを解決できないというエラーが発生するので、登録を追加します。
container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
ConstructorSelector.MostParameters);
しかし、他にもたくさんあります!私は到達します:
container.Register<IDependencyResolver, SimpleInjectorResolver>();
container.Register<IJavaScriptMinifier, NullJavaScriptMinifier>();
container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
ConstructorSelector.MostParameters);
container.Register<IHubManager, DefaultHubManager>();
container.Register<IHubActivator, DefaultHubActivator>();
container.Register<IParameterResolver, DefaultParameterResolver>();
container.Register<IMessageBus, InProcessMessageBus>(ConstructorSelector.MostParameters);
それでも「タイプITraceManager
の登録が見つかりませんでした」というメッセージが表示されます。 ...しかし今、SignalRが実行しているすべてのことを再配線する必要がないことを望んでいるので、これを正しく実行しているかどうか疑問に思っています...そうですか?うまくいけば?そうでなければ、私は歩き続けますが、私はSignalRとSimple Injectorの初心者なので、最初に聞いてみようと思いました。 :)
追加: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=88 SignalRには複数のコンストラクターがあったため。
さて、私は昨日試しました、そして私は解決策を見つけました。私によると、SignalRに依存性注入が必要なのは、ハブだけです。SignalRが内部でどのように機能しているかは気にしません。したがって、DependencyResolverを置き換える代わりに、IHubActivatorの独自の実装を作成しました。
public class SimpleInjectorHubActivator : IHubActivator
{
private readonly Container _container;
public SimpleInjectorHubActivator(Container container)
{
_container = container;
}
public IHub Create(HubDescriptor descriptor)
{
return (IHub)_container.GetInstance(descriptor.HubType);
}
}
このように登録できること(Application_Startで):
var activator = new SimpleInjectorHubActivator(container);
GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
RouteTable.Routes.MapHubs();
SimpleInjectorまたは別のIoCを使用して、SignalRで依存性注入を使用して独自の方法を見つけるのに役立つ、他の回答とともに2セントをここに投入したいと思います。
スティーブンの答えを使用することにした場合は、ルートを作成する前にハブルートを登録してください。 SignalRRouteExtensions.MapHubs
拡張メソッド(別名routes.MapHubs()
)は、ハブルートをマッピングするときにGlobalHost.DependencyResolver
でRegister(Type, Func<object>)
を呼び出すため、DefaultDependencyResolver
を交換するとルートがマップされる前にStevenのSimpleInjectorResolver
を使用すると、彼のNotSupportedException
に遭遇します。
これは私のお気に入りです。どうして?
SimpleInjectorDependencyResolver
よりも少ないコード。DefaultDependencyResolver
(別名GlobalHost.DependencyResolver
)を置き換える必要はありません。つまり、コードがさらに少なくなります。DefaultDependencyResolver
を置き換えていないため、ハブルートのマッピングの前または後にルートを作成できます。これは「正常に機能」します。ナタナエルが言ったように、これはHub
クラスへの依存関係を気にする場合のみであり、おそらくほとんどの場合に当てはまります。 SignalRに他の依存関係を挿入することをいじりたい場合は、Stevenの答えを使用することをお勧めします。
Hub
でのWebリクエストごとの依存関係に関する問題SignalRには興味深い点があります...クライアントがハブから切断すると(たとえば、ブラウザウィンドウを閉じることによって)、OnDisconnected()
。これが発生すると、HttpContext.Current
はnullになります。したがって、このHub
に、Webリクエストごとに登録されている依存関係がある場合、何かがうまくいかない可能性があります。
Ninjectと nugetのninject Signalr依存性リゾルバー を使用してSignalR依存性注入を試しました。この構成では、切断イベント中にHub
に挿入されると、.InRequestScope()
にバインドされた依存関係が一時的に作成されます。 HttpContext.Current
はnullなので、Ninjectはそれを無視して、通知せずに一時的なインスタンスを作成することにしたと思います。 ninjectにこれについて警告するように指示する構成設定があったかもしれませんが、それはデフォルトではありませんでした。
一方、SimpleInjectorは、Hub
がWebRequestLifestlyle
に登録されているインスタンスに依存している場合に例外をスローします。
タイプNameOfYourHubの登録済みデリゲートは、例外をスローしました。タイプNameOfYourPerRequestDependencyの登録済みデリゲートは、例外をスローしました。 YourProject.Namespace.NameOfYourPerRequestDependencyは「PerWebRequest」として登録されていますが、インスタンスはHttpContextのコンテキスト外で要求されています(HttpContext.Currentはnullです)。このライフスタイルを使用するインスタンスが、アプリケーションの初期化フェーズ中およびバックグラウンドスレッドで実行されているときに解決されないようにしてください。バックグラウンドスレッドでインスタンスを解決するには、このインスタンスを「ライフタイムスコープごと」として登録してみてください: https://simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped 。
...この例外は、HttpContext.Current == null
の場合にのみ発生します。これは、私が知る限り、SignalRがOnDisconnected()
を呼び出すためにHub
インスタンスを要求した場合にのみ発生します。
Hub
でのWebリクエストごとの依存関係のソリューションこれらのどれも本当に理想的ではないことに注意してください、それはすべてあなたのアプリケーション要件に依存します。
非一時的な依存関係が必要な場合は、OnDisconnected()
をオーバーライドしたり、クラスの依存関係を使用してカスタムを実行したりしないでください。そうした場合、グラフ内の各依存関係は個別の(一時的な)インスタンスになります。
ハイブリッドライフスタイルWebRequestLifestlye
とLifestyle.Transient
、Lifestyle.Singleton
、またはLifetimeScopeLifestyle
のいずれかが必要です。 HttpContext.Current
がnullでない場合、依存関係は、通常予想されるWeb要求の間だけ存続します。ただし、HttpContext.Current
がnullの場合、依存関係は一時的に、シングルトンとして、またはライフタイムスコープ内に注入されます。
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
インスタンスが作成された直後に破棄されます。ここで本当に必要なのは、別の間接層です。
スティーブンの助けのおかげで、私が最終的に得たのは、コマンドデコレータ(およびクエリデコレータ)です。 Hub
は、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);
}
}
}
... Webリクエストごとのインスタンスに依存するのは、装飾されたICommandHandler<T>
インスタンスです。使用されるパターンの詳細については、 this および this を参照してください。
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), assemblies);
container.RegisterSingleDecorator(
typeof(ICommandHandler<>),
typeof(CommandLifetimeScopeDecorator<>)
);
[〜#〜] update [〜#〜]この回答SignalRバージョン1.0用に更新されました
SimpleInjector用のSignalRIDependencyResolver
を作成する方法は次のとおりです。
public sealed class SimpleInjectorResolver
: Microsoft.AspNet.SignalR.IDependencyResolver
{
private Container container;
private IServiceProvider provider;
private DefaultDependencyResolver defaultResolver;
public SimpleInjectorResolver(Container container)
{
this.container = container;
this.provider = container;
this.defaultResolver = new DefaultDependencyResolver();
}
[DebuggerStepThrough]
public object GetService(Type serviceType)
{
// Force the creation of hub implementation to go
// through Simple Injector without failing silently.
if (!serviceType.IsAbstract && typeof(IHub).IsAssignableFrom(serviceType))
{
return this.container.GetInstance(serviceType);
}
return this.provider.GetService(serviceType) ??
this.defaultResolver.GetService(serviceType);
}
[DebuggerStepThrough]
public IEnumerable<object> GetServices(Type serviceType)
{
return this.container.GetAllInstances(serviceType);
}
public void Register(Type serviceType, IEnumerable<Func<object>> activators)
{
throw new NotSupportedException();
}
public void Register(Type serviceType, Func<object> activator)
{
throw new NotSupportedException();
}
public void Dispose()
{
this.defaultResolver.Dispose();
}
}
残念ながら、DefaultDependencyResolver
の設計に問題があります。そのため、上記の実装はそれを継承せず、ラップします。 SignalRサイトでこれに関する問題を作成しました。あなたはそれについて読むことができます ここ 。設計者は私に同意しましたが、残念ながらこの問題はバージョン1.0では修正されていません。
これがお役に立てば幸いです。
SignalR 2.0(およびベータ版)から、依存関係リゾルバーを設定する新しい方法があります。 SignalRは、構成を行うためにOWINスタートアップに移動しました。 Simple Injectorを使用すると、次のようになります。
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HubConfiguration()
{
Resolver = new SignalRSimpleInjectorDependencyResolver(Container)
};
app.MapSignalR(config);
}
}
public class SignalRSimpleInjectorDependencyResolver : DefaultDependencyResolver
{
private readonly Container _container;
public SignalRSimpleInjectorDependencyResolver(Container container)
{
_container = container;
}
public override object GetService(Type serviceType)
{
return ((IServiceProvider)_container).GetService(serviceType)
?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _container.GetAllInstances(serviceType)
.Concat(base.GetServices(serviceType));
}
}
次のようにハブを明示的に注入する必要があります。
container.Register<MessageHub>(() => new MessageHub(new EFUnitOfWork()));
この構成は、トラフィックの多いWebサイトで問題なくライブで実行されています。
.NET Core 3.x
signalRはTransientになりました。 DIを使用してハブを注入できます。したがって、ハブのマッピングを開始し、インターフェイスとハブを実装し、DIを介してそのコンテキストにアクセスします。
起動:
app.UseSignalR(routes =>
{
routes.MapHub<YourHub>(NotificationsRoute); // defined as string
});
services.AddSignalR(hubOptions =>
{
// your options
})
次に、次のようなインターフェイスを実装します。
public interface IYourHub
{
// Your interface implementation
}
そしてあなたのハブ:
public class YourHub : Hub<IYourHub>
{
// your hub implementation
}
最後に、次のようにハブを注入します。
private IHubContext<YourHub, IYourHub> YourHub
{
get
{
return this.serviceProvider.GetRequiredService<IHubContext<YourHub, IYourHub>>();
}
}
また、ハブ(ミドルウェア)のサービスを定義し、コンテキストをクラスに直接注入しないようにすることもできます。
インターフェイスでメソッドMessage
を定義したと想像してください。そうすれば、クラスで次のようなメッセージを送信できます。
await this.YourHub.Clients.Group("someGroup").Message("Some Message").ConfigureAwait(false);
使用するインターフェースに実装されていない場合:
await this.YourHub.Clients.Group("someGroup").SendAsync("Method Name", "Some Message").ConfigureAwait(false);
以下は私のために働いた。さらに、依存関係リゾルバーをインスタンス化する前に、ハブクラスのコンテナーにデリゲートを登録する必要があります。
ex: container.Register<MyHub>(() =>
{
IMyInterface dependency = container.GetInstance<IMyInterface>();
return new MyHub(dependency);
});
public class SignalRDependencyResolver : DefaultDependencyResolver
{
private Container _container;
private HashSet<Type> _types = new HashSet<Type>();
public SignalRDependencyResolver(Container container)
{
_container = container;
RegisterContainerTypes(_container);
}
private void RegisterContainerTypes(Container container)
{
InstanceProducer[] producers = container.GetCurrentRegistrations();
foreach (InstanceProducer producer in producers)
{
if (producer.ServiceType.IsAbstract || producer.ServiceType.IsInterface)
continue;
if (!_types.Contains(producer.ServiceType))
{
_types.Add(producer.ServiceType);
}
}
}
public override object GetService(Type serviceType)
{
return _types.Contains(serviceType) ? _container.GetInstance(serviceType) : base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _types.Contains(serviceType) ? _container.GetAllInstances(serviceType) : base.GetServices(serviceType);
}
}