web-dev-qa-db-ja.com

SignalRでのシンプルなインジェクターの使用

自分の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には複数のコンストラクターがあったため。

33
rball

さて、私は昨日試しました、そして私は解決策を見つけました。私によると、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();
44

SimpleInjectorまたは別のIoCを使用して、SignalRで依存性注入を使用して独自の方法を見つけるのに役立つ、他の回答とともに2セントをここに投入したいと思います。

使用 @スティーブンの答え

スティーブンの答えを使用することにした場合は、ルートを作成する前にハブルートを登録してください。 SignalRRouteExtensions.MapHubs拡張メソッド(別名routes.MapHubs())は、ハブルートをマッピングするときにGlobalHost.DependencyResolverRegister(Type, Func<object>)を呼び出すため、DefaultDependencyResolverを交換するとルートがマップされる前にStevenのSimpleInjectorResolverを使用すると、彼のNotSupportedExceptionに遭遇します。

使用 @ Nathanael Marchandの回答

これは私のお気に入りです。どうして?

  1. SimpleInjectorDependencyResolverよりも少ないコード。
  2. DefaultDependencyResolver(別名GlobalHost.DependencyResolver)を置き換える必要はありません。つまり、コードがさらに少なくなります。
  3. DefaultDependencyResolverを置き換えていないため、ハブルートのマッピングの前または後にルートを作成できます。これは「正常に機能」します。

ナタナエルが言ったように、これはHubクラスへの依存関係を気にする場合のみであり、おそらくほとんどの場合に当てはまります。 SignalRに他の依存関係を挿入することをいじりたい場合は、Stevenの答えを使用することをお勧めします。

HubでのWebリクエストごとの依存関係に関する問題

SignalRには興味深い点があります...クライアントがハブから切断すると(たとえば、ブラウザウィンドウを閉じることによって)、OnDisconnected()。これが発生すると、HttpContext.Currentはnullになります。したがって、このHubに、Webリクエストごとに登録されている依存関係がある場合、何かがうまくいかない可能性があります

Ninjectで

Ninjectと nugetのninject Signalr依存性リゾルバー を使用してSignalR依存性注入を試しました。この構成では、切断イベント中にHubに挿入されると、.InRequestScope()にバインドされた依存関係が一時的に作成されます。 HttpContext.Currentはnullなので、Ninjectはそれを無視して、通知せずに一時的なインスタンスを作成することにしたと思います。 ninjectにこれについて警告するように指示する構成設定があったかもしれませんが、それはデフォルトではありませんでした。

SimpleInjectorで

一方、SimpleInjectorは、HubWebRequestLifestlyleに登録されているインスタンスに依存している場合に例外をスローします。

タイプ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リクエストごとの依存関係のソリューション

これらのどれも本当に理想的ではないことに注意してください、それはすべてあなたのアプリケーション要件に依存します。

Ninjectで

非一時的な依存関係が必要な場合は、OnDisconnected()をオーバーライドしたり、クラスの依存関係を使用してカスタムを実行したりしないでください。そうした場合、グラフ内の各依存関係は個別の(一時的な)インスタンスになります。

SimpleInjectorで

ハイブリッドライフスタイルWebRequestLifestlyeLifestyle.TransientLifestyle.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<>)
);
24
danludwig

[〜#〜] 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では修正されていません。

これがお役に立てば幸いです。

5
Steven

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サイトで問題なくライブで実行されています。

3

.NET Core 3.xsignalRは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);
0
Kiril1512

以下は私のために働いた。さらに、依存関係リゾルバーをインスタンス化する前に、ハブクラスのコンテナーにデリゲートを登録する必要があります。

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