SignalR hub中每个web-api-request依赖的简单注入器
本文关键字:依赖 简单 注入器 web-api-request hub SignalR | 更新日期: 2023-09-27 18:07:04
根据这篇文章,应该可以将每个web请求的依赖项注入SignalR集线器(尽管有一些限制,如OnDisconnected()方法的问题)。在我的情况下,它是ASP Web API(不是MVC),它不工作的某些原因。
相关部分如下:
container.RegisterWebApiControllers(httpConfiguration);
container.RegisterWebApiRequest<DbContext, MyDbContext>();
container.RegisterWebApiRequest<ISampleRepository, SampleRepository>(); //DbContext injected to SampleRepository
//Enable injections to SignalR Hubs
var activator = new SimpleInjectorHubActivator(container);
GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
这个类可以注入到hub:
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);
}
}
和Hub本身:
[HubName("sample")]
public class SampleHub : Hub
{
public ActiveBetsHub(ISampleRepository repository)
{
}
//Irrelevant methods here. OnDisconnected() NOT implemented!
}
使用这个设置,我得到exception:
No registration for type SampleHub could be found and
an implicit registration could not be made.
The ISampleRepository is registered as 'Web API Request'
lifestyle, but the instance is requested outside the context of a Web API Request.
这是我所理解的。然而,当我将存储库的生活方式更改为Transient时,我得到了完全相同的异常:
var transientHybrid = Lifestyle.CreateHybrid(() => HttpContext.Current != null, new WebApiRequestLifestyle(), Lifestyle.Transient);
container.Register<ISampleRepository, SampleRepository>(transientHybrid);
我怀疑问题可能在于HttpContext.Current != null
检查不适合Web API,就像MVC一样。
Simple Injector 2.8.3
我错过了什么?
更新:
这是关于SignalR如何创建hub的堆栈跟踪:
at SimpleInjector.InstanceProducer.GetInstance()
at SimpleInjector.Container.GetInstance(Type serviceType)
at MyWebAbi.WebApiApplication.SimpleInjectorHubActivator.Create(HubDescriptor descriptor) in Global.asax.cs:line 108
at Microsoft.AspNet.SignalR.Hubs.DefaultHubManager.ResolveHub(String hubName)
at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.CreateHub(IRequest request, HubDescriptor descriptor, String connectionId, StateChangeTracker tracker, Boolean throwIfFailedToCreate)
所以正确的解决方案是使用ExecutionContextScope
为一个集线器,但这个范围需要明确关闭,这使事情变得更加复杂…
你对混合生活方式的定义是不正确的。WebApiRequestLifestyle
不以任何方式依赖于HttpContext
,所以检查HttpContext.Current != null
是否不能工作。您必须通过调用container.GetCurrentExecutionContextScope()
:
var transientHybrid = Lifestyle.CreateHybrid(
() => container.GetCurrentExecutionContextScope() != null,
new WebApiRequestLifestyle(),
Lifestyle.Transient);
但是请注意,应该非常小心地组成作用域的生活方式和暂态的生活方式的混合生活方式,因为这很容易产生错误的结果。这实际上是一些DI库的默认行为,但在我看来这是一个设计缺陷。我假设您非常有意识地将MyDbContext
注册为有作用域的生活方式,因为您需要确保在整个请求中使用相同的实例。使用Transient
方式意味着您可能在请求期间获得多个MyDbContext
。这可能不是问题,因为在您的集线器中,您可能目前只有一个对MyDbContext
的引用,但是一旦您的对象图发生变化并且添加了第二个对MyDbContext
的引用,您的代码可能会中断。
因此,我建议不要使用这种生活方式组合。相反,只需使用WebApiRequestLifestyle
或ExecutionContextScopeLifestyle
(它们是相同的),并确保在解析集线器之前启动这样的执行上下文范围。
最近我遇到了同样的问题,发现下面的工作很好,希望这能帮助到别人:
public class SignalRDependencyResolver : DefaultDependencyResolver
{
public SignalRDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public override object GetService(Type serviceType)
{
return _serviceProvider.GetService(serviceType) ?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
var @this = (IEnumerable<object>) _serviceProvider.GetService(typeof (IEnumerable<>).MakeGenericType(serviceType));
var @base = base.GetServices(serviceType);
return @this == null ? @base : @base == null ? @this : @this.Concat(@base);
}
private readonly IServiceProvider _serviceProvider;
}
public class SignalRHubDispatcher : HubDispatcher
{
public SignalRHubDispatcher(Container container, HubConfiguration configuration) : base(configuration)
{
_container = container;
}
protected override Task OnConnected(IRequest request, string connectionId)
{
return Invoke(() => base.OnConnected(request, connectionId));
}
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
return Invoke(() => base.OnReceived(request, connectionId, data));
}
protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
{
return Invoke(() => base.OnDisconnected(request, connectionId, stopCalled));
}
protected override Task OnReconnected(IRequest request, string connectionId)
{
return Invoke(() => base.OnReconnected(request, connectionId));
}
private async Task Invoke(Func<Task> method)
{
using (_container.BeginExecutionContextScope())
await method();
}
private readonly Container _container;
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
container.Register<DbContext, MyDbContext>(Lifestyle.Scoped);
container.Register<ISampleRepository, SampleRepository>(Lifestyle.Scoped);
// if you want to use the same container in WebApi don't forget to add
app.Use(async (context, next) => {
using (container.BeginExecutionContextScope())
await next();
});
// ... configure web api
var config = new HubConfiguration
{
Resolver = new SignalRDependencyResolver(container)
}
// ... configure the rest of SignalR
// pass SignalRHubDispatcher
app.MapSignalR<SignalRHubDispatcher>("/signalr", config);
}
}