SignalR hub 中每个网络的简单注入器-api-请求依赖
Simple Injector per-web-api-request dependency in SignalR hub
根据 this post,应该可以将每个 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);
这 class 可以注入集线器:
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!
}
使用此设置我得到异常:
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.
据我了解,这是意料之中的事情。但是,当我将存储库的 Lifestyle 更改为 Transient:
时,我得到了完全相同的异常
var transientHybrid = Lifestyle.CreateHybrid(() => HttpContext.Current != null, new WebApiRequestLifestyle(), Lifestyle.Transient);
container.Register<ISampleRepository, SampleRepository>(transientHybrid);
我怀疑问题可能出在 HttpContext.Current != null
检查 Web API 是否以与 MVC 相同的方式工作。
SignalR 2.2
简单注入器 2.8.3
我错过了什么?
更新:
这是关于 SignalR 如何创建集线器的堆栈跟踪:
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()
:
来检查是否存在活动的 Web API 请求生活方式范围(或执行上下文范围,基本相同)
var transientHybrid = Lifestyle.CreateHybrid(
() => container.GetCurrentExecutionContextScope() != null,
new WebApiRequestLifestyle(),
Lifestyle.Transient);
但是请注意,您应该非常小心组合范围生活方式和短暂生活方式的混合生活方式,因为这很容易产生错误的结果。这实际上是某些 DI 库的默认行为,但这是 design flaw IMO。我假设您非常有意识地将您的 MyDbContext
注册到 scoped 生活方式,因为您需要确保在整个请求中使用相同的实例。使用 Transient
生活方式意味着您可能会在请求期间获得多个 MyDbContext
。这可能不是问题,因为在您的集线器中,您目前可能只有一个对 MyDbContext
的引用,但是一旦您的对象图发生变化并且添加了对 MyDbContext
的第二个引用,您的代码可能会中断。
因此,我建议不要使用这种生活方式的组合。相反,只需使用 WebApiRequestLifestyle
或 ExecutionContextScopeLifestyle
(它们相同)并确保在解析集线器之前启动此类执行上下文范围。
顺便说一句,不要忘记在 Simple Injector 中显式注册您的集线器。这允许 Simple Injector 为您分析完整的对象图,包括您的集线器 类.
最近我遇到了同样的问题,发现以下方法工作得很好,希望这对某人有所帮助:
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);
}
}
根据 this post,应该可以将每个 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);
这 class 可以注入集线器:
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!
}
使用此设置我得到异常:
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.
据我了解,这是意料之中的事情。但是,当我将存储库的 Lifestyle 更改为 Transient:
时,我得到了完全相同的异常 var transientHybrid = Lifestyle.CreateHybrid(() => HttpContext.Current != null, new WebApiRequestLifestyle(), Lifestyle.Transient);
container.Register<ISampleRepository, SampleRepository>(transientHybrid);
我怀疑问题可能出在 HttpContext.Current != null
检查 Web API 是否以与 MVC 相同的方式工作。
SignalR 2.2
简单注入器 2.8.3
我错过了什么?
更新:
这是关于 SignalR 如何创建集线器的堆栈跟踪:
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 库的默认行为,但这是 design flaw IMO。我假设您非常有意识地将您的 MyDbContext
注册到 scoped 生活方式,因为您需要确保在整个请求中使用相同的实例。使用 Transient
生活方式意味着您可能会在请求期间获得多个 MyDbContext
。这可能不是问题,因为在您的集线器中,您目前可能只有一个对 MyDbContext
的引用,但是一旦您的对象图发生变化并且添加了对 MyDbContext
的第二个引用,您的代码可能会中断。
因此,我建议不要使用这种生活方式的组合。相反,只需使用 WebApiRequestLifestyle
或 ExecutionContextScopeLifestyle
(它们相同)并确保在解析集线器之前启动此类执行上下文范围。
顺便说一句,不要忘记在 Simple Injector 中显式注册您的集线器。这允许 Simple Injector 为您分析完整的对象图,包括您的集线器 类.
最近我遇到了同样的问题,发现以下方法工作得很好,希望这对某人有所帮助:
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);
}
}