使用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
,因此如果在路由之前将SimpleInjectorResolver
的SimpleInjectorResolver
替换为DefaultDependencyResolver
映射后,您将遇到他的NotSupportedException
。
使用@Nathanael Marchand的回答
这是我最喜欢的。 为什么?
- 代码少于
SimpleInjectorDependencyResolver
。 - 无需替换
DefaultDependencyResolver
(又名GlobalHost.DependencyResolver
),这意味着更少的代码。 - 您可以在映射中心路由之前或之后组成根,因为您没有替换
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中
您需要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
实例的实体时结束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
不幸的是, 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); } }