简体   繁体   English

SignalR hub中的简单Injector per-web-api-request依赖

[英]Simple Injector per-web-api-request dependency in SignalR hub

According to this post, it should be possible to inject per-web-request dependencies into SignalR hubs (although with some limitations like problem with OnDisconnected() method). 根据这篇文章,应该可以将每个Web请求依赖项注入SignalR集线器(尽管有一些限制,如OnDisconnected()方法的问题)。 In my case it is ASP Web API (not MVC) and it does not work for some reason. 在我的例子中,它是ASP Web API(而不是MVC),并且由于某种原因它不起作用。

Here are relevant parts: 以下是相关部分:

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

This class makes possible to inject into hubs: 这个类可以注入集线器:

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

And Hub itself: 和Hub本身:

 [HubName("sample")]
 public class SampleHub : Hub
    {

        public ActiveBetsHub(ISampleRepository repository)
        {
        }

        //Irrelevant methods here. OnDisconnected() NOT implemented!
    }

With this setup I get 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.

Which is expected as I understand. 据我所知,这是预期的。 However I get exactly same exception when I change Lifestyle of repository to Transient: 但是当我将存储库的生活方式更改为Transient时,我得到完全相同的异常:

    var transientHybrid = Lifestyle.CreateHybrid(() => HttpContext.Current != null, new WebApiRequestLifestyle(), Lifestyle.Transient);
    container.Register<ISampleRepository, SampleRepository>(transientHybrid);

I suspect the problem could lie in HttpContext.Current != null check that is not working for Web API the same way as for MVC. 我怀疑问题可能在于HttpContext.Current != null检查不适用于Web API,与MVC相同。

SignalR 2.2 SignalR 2.2

Simple Injector 2.8.3 简单的注射器2.8.3

What do I miss? 我错过了什么?

UPDATE: 更新:

This is stack trace on how SignalR creates Hubs: 这是关于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)

So the proper solution would be to use ExecutionContextScope for a Hubs but this scope needs to be explicitly closed which makes things more complicated... 因此,正确的解决方案是将ExecutionContextScope用于Hub,但是这个范围需要明确关闭,这会使事情变得更复杂......

Your definition of your hybrid lifestyle is incorrect. 您对混合生活方式的定义不正确。 The WebApiRequestLifestyle does not depend in any way on the HttpContext so checking whether HttpContext.Current != null will not work. WebApiRequestLifestyle不以任何方式依赖于HttpContext因此检查HttpContext.Current != null是否不起作用。 You will have to check if there is an active Web API request lifestyle scope (or execution context scope, which is basically the same) by calling container.GetCurrentExecutionContextScope() : 您必须通过调用container.GetCurrentExecutionContextScope()来检查是否存在活动的Web API请求生活方式范围(或执行上下文范围,这基本上是相同的container.GetCurrentExecutionContextScope()

var transientHybrid = Lifestyle.CreateHybrid(
    () => container.GetCurrentExecutionContextScope() != null, 
    new WebApiRequestLifestyle(), 
    Lifestyle.Transient);

Do note however that you should be very careful composing a hybrid lifestyle of a scoped lifestyle and transient, because this will easily yield in wrong results. 但请注意,你应该非常小心地构成一个混乱的生活方式和短暂的生活方式,因为这很容易产生错误的结果。 This is actually the default behavior of some DI libraries, but this is a design flaw IMO. 这实际上是一些DI库的默认行为,但这是一个设计缺陷 IMO。 I assume you very consciously registered your MyDbContext with the scoped lifestyle, because you need to make sure that the same instance is used throughout the request. 我假设您非常有意识地使用范围生活方式注册了MyDbContext ,因为您需要确保在整个请求中使用相同的实例。 Using the Transient lifestyle means that you might get multiple MyDbContext during the request. 使用Transient生活方式意味着您可以在请求期间获得多个MyDbContext This might not be a problem, because in your hubs you might currently only have one reference to your MyDbContext , but your code might break once your object graph changes and a second reference to MyDbContext is added. 这可能不是问题,因为在您的集线器中,您当前可能只有一个对MyDbContext引用,但是一旦对象图更改并且添加了对MyDbContext的第二个引用,您的代码可能会中断。

So instead, I would advice not using this combination of lifestyles. 相反,我建议不要使用这种生活方式的组合。 Instead, just use either the WebApiRequestLifestyle or the ExecutionContextScopeLifestyle (they are the same) and make sure that such a execution context scope is started before your hub is resolved. 相反,只需使用WebApiRequestLifestyleExecutionContextScopeLifestyle (它们是相同的),并确保在解析集线器之前启动此类执行上下文范围。

And by the way, don't forget to register your hubs explicitly in Simple Injector. 顺便说一句,不要忘记在Simple Injector中明确注册您的集线器。 This allows Simple Injector to analyze the complete object graph for you including your hub classes. 这允许Simple Injector为您分析完整的对象图,包括您的集线器类。

Recently I faced the same problem and found the following working quite well, hope this will help someone: 最近我遇到了同样的问题,发现以下工作得很好,希望这会对某人有所帮助:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM