使用SimpleInjector和SignalR

我认为使用我自己的IoC对SignalR非常简单,也许是; 我很可能做错了什么。 这是我到目前为止的代码:

private static void InitializeContainer(Container container) { container.Register<IMongoHelper, MongoHelper>(); // ... registrations like about and then: var resolver = new SimpleInjectorResolver(container); GlobalHost.DependencyResolver = resolver; } 

然后我的class级:

 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 GetServices(Type serviceType) { return _container.GetAllInstances(serviceType) ?? base.GetServices(serviceType); } } 

最终发生的事情是我得到一个IJavaScriptProxyGenerator无法解决的错误,所以我想,我会添加注册:

 container.Register( ConstructorSelector.MostParameters); 

但是还有其他一些人! 我到达:

 container.Register(); container.Register(); container.Register( ConstructorSelector.MostParameters); container.Register(); container.Register(); container.Register(); container.Register(ConstructorSelector.MostParameters); 

这仍然给我“没有注册类型ITraceManager可以找到。” ……但是现在我想知道我是否正在做这件事,因为我希望我不需要重新连接SignalR所做的一切……对吗? 希望? 如果不是,我会继续跋涉,但我是一个SignalR和简单的注射器newb所以我想先问。 🙂

附加: https ://cuttingedge.it/blogs/steven/pivot/entry.php ? id = 88,因为SignalR有多个构造函数。

好吧,我昨天试过,我找到了解决方案。 根据我的说法,我想在SignalR中进行dependency injection的唯一时刻是我的集线器:我不关心SignalR如何在内部工作! 因此,我创建了自己的IHubActivator实现,而不是替换DependencyResolver:

 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(); 

想要在这里与其他答案一起投入2美分,这有助于在SignalR中使用SimpleInjector或其他IoC找到自己的dependency injection方式。

使用@ Steven的答案

如果您决定使用Steven的答案,请确保在撰写根目录之前注册中心路由。 映射中心路由时, SignalRRouteExtensions.MapHubs扩展方法(aka routes.MapHubs() )将在SignalRRouteExtensions.MapHubs调用Register(Type, Func) ,因此如果在路由之前将SimpleInjectorResolverSimpleInjectorResolver替换为DefaultDependencyResolver映射后,您将遇到他的NotSupportedException

使用@Nathanael Marchand的回答

这是我最喜欢的。 为什么?

  1. 代码少于SimpleInjectorDependencyResolver
  2. 无需替换DefaultDependencyResolver (又名GlobalHost.DependencyResolver ),这意味着更少的代码。
  3. 您可以在映射中心路由之前或之后组成根,因为您没有替换DefaultDependencyResolver ,它将“正常工作”。

就像Nathanael所说的那样,只有当你关心你的Hub类的依赖关系时才会这样,大多数人都可能会这样。 如果你想在SignalR中注入其他依赖项,你可能想要使用Steven的答案。

Hub每个Web请求依赖项的问题

SignalR有一个有趣的事情……当客户端从集线器断开连接时(例如关闭浏览器窗口),它将创建一个Hub类的新实例,以便调用OnDisconnected() 。 发生这种情况时, HttpContext.Current为null 。 因此,如果此Hub具有按Web请求注册的任何依赖项,则可能会出现问题

在Ninject

我尝试使用Ninject和nuget上的ninject signalr依赖解析器进行SignalRdependency injection。 使用此配置,在断开连接事件期间注入到Hub中时,将临时创建绑定.InRequestScope()依赖项。 由于HttpContext.Current为null,我想Ninject只是决定忽略它并在不告诉你的情况下创建瞬态实例。 也许有一个配置设置告诉ninject警告这个,但它不是默认值。

在SimpleInjector中

另一方面,当Hub依赖于在WebRequestLifestlyle注册的实例时,SimpleInjector将抛出exception:

NameOfYourHub类型的已注册委托引发了exception。 NameOfYourPerRequestDependency类型的已注册委托引发了exception。 YourProject.Namespace.NameOfYourPerRequestDependency注册为“PerWebRequest”,但是在HttpContext的上下文之外请求实例(HttpContext.Current为null)。 确保在应用程序初始化阶段和在后台线程上运行时,不会解析使用此生活方式的实例。 要解析后台线程上的实例,请尝试将此实例注册为“Per Lifetime Scope”: https : //simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped 。

…注意这个exception只会在HttpContext.Current == null时冒泡,据我所知,只有在SignalR请求Hub实例才能调用OnDisconnected()时才会出现这种情况。

Hub每个Web请求依赖项的解决方案

请注意,这些都不是真正理想的,它将取决于您的应用要求。

在Ninject

如果您需要非瞬态依赖项,请不要重写OnDisconnected()或使用类依赖项执行任何自定义操作。 如果这样做,图中的每个依赖项将是一个单独的(瞬态)实例。

在SimpleInjector中

您需要WebRequestLifestlyeLifestyle.TransientLifestyle.SingletonLifetimeScopeLifestyle之间的混合生活方式 。 当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实例的实体时结束exception。 注册为单例时,最终会有更多常规exception(不要将DbContext注册为单例)。 在我的情况下,我需要DbContext生活在特定的生命周期内,在这个生命周期中,可以在许多嵌套操作中重用相同的实例,这意味着我需要LifetimeScopeLifestyle

现在,如果您使用上面的混合代码与falseLifestyle: new LifetimeScopeLifestyle()行,当您的自定义IHubActivator.Create方法执行时,您将获得另一个exception:

NameOfYourHub类型的已注册委托引发了exception。 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实例后立即处理。 你真正需要的是另一层间接。

我非常感谢Steven的帮助,是一个命令装饰器(以及一个查询装饰器)。 Hub不能依赖于每个Web请求实例本身,而必须依赖于另一个接口,其实现取决于每个请求实例。 注入到Hub构造函数中的实现将使用包装器进行修饰(通过simpleinjector),该包装器开始并处理生命周期范围。

 public class CommandLifetimeScopeDecorator : ICommandHandler { private readonly Func> _handlerFactory; private readonly Container _container; public CommandLifetimeScopeDecorator( Func> 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实例。 有关所用模式的更多信息,请阅读此内容 。

注册示例

 container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), assemblies); container.RegisterSingleDecorator( typeof(ICommandHandler<>), typeof(CommandLifetimeScopeDecorator<>) ); 

更新此答案已针对SignalR 1.0版进行了更新

这是为Simple Injector构建SignalR IDependencyResolver的方法:

 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 GetServices(Type serviceType) { return this.container.GetAllInstances(serviceType); } public void Register(Type serviceType, IEnumerable> activators) { throw new NotSupportedException(); } public void Register(Type serviceType, Func activator) { throw new NotSupportedException(); } public void Dispose() { this.defaultResolver.Dispose(); } } 

不幸的是, DefaultDependencyResolver的设计存在问题。 这就是为什么上面的实现不inheritance它,而是包装它。 我在SignalR网站上创建了一个关于此的问题。 你可以在这里阅读它。 虽然设计师同意我的意见,但不幸的是1.0版本中没有修复该问题。

我希望这有帮助。

从SignalR 2.0(以及beta版)开始,有一种设置依赖关系解析器的新方法。 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 GetServices(Type serviceType) { return _container.GetAllInstances(serviceType) .Concat(base.GetServices(serviceType)); } } 

您必须明确注入您的集线器,如下所示:

 container.Register(() => new MessageHub(new EFUnitOfWork())); 

此配置在高流量网站上正常运行,没有任何问题。

以下对我有用。 此外,在实例化依赖项解析程序之前,您需要为集线器类的容器注册委托。

 ex: container.Register(() => { IMyInterface dependency = container.GetInstance(); return new MyHub(dependency); }); public class SignalRDependencyResolver : DefaultDependencyResolver { private Container _container; private HashSet _types = new HashSet(); 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 GetServices(Type serviceType) { return _types.Contains(serviceType) ? _container.GetAllInstances(serviceType) : base.GetServices(serviceType); } }