想把我的 2 美分和其他答案一起扔在这里,这有助于在 SignalR 中找到自己的依赖注入方式,无论是使用 SimpleInjector 还是其他 IoC。
如果您决定使用 Steven 的答案,请确保在编写根之前注册您的集线器路由。SignalRRouteExtensions.MapHubs
扩展方法(aka routes.MapHubs()
)将在映射中心路由时调用,Register(Type, Func<object>)
因此GlobalHost.DependencyResolver
如果您在映射路由之前将DefaultDependencyResolver
与 Steven's交换SimpleInjectorResolver
,您将遇到他的NotSupportedException
.
这是我最喜欢的。为什么?
- 代码比
SimpleInjectorDependencyResolver
.
- 无需替换
DefaultDependencyResolver
(aka GlobalHost.DependencyResolver
),这意味着更少的代码。
- 您可以在映射集线器路由之前或之后组成根,因为您没有替换
DefaultDependencyResolver
,它将“正常工作”。
就像 Nathanael 说的那样,只有当你关心你的Hub
类的依赖时,这可能是大多数人的情况。如果您想将其他依赖项注入到 SignalR 中,您可能想要使用 Steven 的答案。
中每个 Web 请求依赖项的问题Hub
SignalR 有一个有趣的事情......当客户端与集线器断开连接时(例如通过关闭他们的浏览器窗口),它将创建Hub
该类的一个新实例以调用OnDisconnected()
. 发生这种情况时,HttpContext.Current
为 null。因此,如果这Hub
有任何按网络请求注册的依赖项,则可能会出错。
在忍者
我在 nuget 上使用 Ninject 和ninject signalr 依赖解析器尝试了 SignalR 依赖注入。使用此配置,绑定的依赖项将在断开连接事件期间.InRequestScope()
注入到 a 时临时创建。Hub
由于HttpContext.Current
为空,我想 Ninject 只是决定忽略它并创建瞬态实例而不告诉你。也许有一个配置设置告诉 ninject 对此发出警告,但这不是默认设置。
在 SimpleInjector 中
另一方面,当 aHub
依赖于注册的实例时,SimpleInjector 将引发异常WebRequestLifestlyle
:
NameOfYourHub 类型的注册委托引发了异常。NameOfYourPerRequestDependency 类型的注册委托引发了异常。YourProject.Namespace.NameOfYourPerRequestDependency 注册为“PerWebRequest”,但在 HttpContext 的上下文之外请求实例(HttpContext.Current 为空)。确保在应用程序初始化阶段和在后台线程上运行时不会解析使用这种生活方式的实例。要解析后台线程上的实例,请尝试将此实例注册为“Per Lifetime Scope”:https ://simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped 。
...请注意,此异常只会在 时冒泡HttpContext.Current == null
,据我所知,仅在 SignalR 请求Hub
实例以调用时才会发生OnDisconnected()
。
解决方案中每个 Web 请求的依赖关系Hub
请注意,这些都不是真正理想的,这完全取决于您的应用程序要求。
在忍者
如果您需要非瞬态依赖项,请不要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
就我而言,我有一个 EntityFrameworkDbContext
依赖项。这些可能很棘手,因为它们可能会在临时注册或作为单例注册时暴露问题。临时注册时,您可能会在尝试使用附加到 2 个或更多DbContext
实例的实体时遇到异常。当注册为单例时,您最终会遇到更一般的异常(永远不要将 a 注册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;
}
我问过 Steven(如果你不知道的话,他也是 SimpleInjector 的作者),他说:
嗯.. 如果你不处理 LifetimeScope,你会遇到大麻烦,所以要确保它们被处理掉。如果您不处置范围,它们将在 ASP.NET 中永远存在。这是因为作用域可以嵌套并引用它们的父作用域。因此,线程保持最内部的范围(及其缓存)处于活动状态,并且该范围保持其父范围(及其缓存)处于活动状态,依此类推。ASP.NET 将线程池化,并且在从池中获取线程时不会重置所有值,因此这意味着所有范围都保持活动状态,并且下次从池中获取线程并开始新的生命周期范围时,您只需简单地创建一个新的嵌套范围,这将继续堆叠。迟早,您会收到 OutOfMemoryException。
您不能使用IHubActivator
来确定依赖项的范围,因为它不会像Hub
它创建的实例那样存在。因此,即使您将BeginLifetimeScope()
方法包装在一个using
块中,您的依赖项也会在Hub
创建实例后立即释放。您在这里真正需要的是另一层间接。
非常感谢 Steven 的帮助,我最终得到了一个命令装饰器(和一个查询装饰器)。AHub
不能依赖于 per-web-request 实例本身,而是必须依赖于另一个接口,其实现取决于 per-request 实例。注入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>
依赖于每个网络请求实例的装饰实例。有关所用模式的更多信息,请阅读this和this。
示例注册
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), assemblies);
container.RegisterSingleDecorator(
typeof(ICommandHandler<>),
typeof(CommandLifetimeScopeDecorator<>)
);