使用SimpleInjector和SignalR

时间:2012-05-11 17:12:59

标签: c# signalr simple-injector

我认为使用我自己的IoC对SignalR来说非​​常简单,也许就是这样;我很可能做错了什么。这是我到目前为止的代码:

private static void InitializeContainer(Container container)
{

   container.Register<IMongoHelper<UserDocument>, MongoHelper<UserDocument>>();
   // ... registrations like about and then:
   var resolver = new SimpleInjectorResolver(container);
   GlobalHost.DependencyResolver = resolver;
}

然后我的班级:

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<object> GetServices(Type serviceType)
    {
        return _container.GetAllInstances(serviceType) ?? base.GetServices(serviceType);
    }
}

最终发生的事情是我收到IJavaScriptProxyGenerator无法解决的错误,所以我想,我会添加注册:

container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
    ConstructorSelector.MostParameters);

但是还有很多其他人!我到了:

container.Register<IDependencyResolver, SimpleInjectorResolver>();
container.Register<IJavaScriptMinifier, NullJavaScriptMinifier>();
container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
    ConstructorSelector.MostParameters);
container.Register<IHubManager, DefaultHubManager>();
container.Register<IHubActivator, DefaultHubActivator>();
container.Register<IParameterResolver, DefaultParameterResolver>();
container.Register<IMessageBus, InProcessMessageBus>(ConstructorSelector.MostParameters);

仍然可以找到“无法注册类型ITraceManager。” ......但是现在我想知道我是否正在做这件事,因为我希望我不需要重新连接SignalR所做的一切......对吗?希望?如果不是,我会继续跋涉,但我是一个SignalR和简单的注射器newb所以我想我先问。 :)

附加:https://cuttingedge.it/blogs/steven/pivot/entry.php?id=88,因为SignalR有多个构造函数。

5 个答案:

答案 0 :(得分:43)

好吧,我昨天试过,我找到了一个解决方案。 据我所知,我想在SignalR中进行依赖注入的唯一时刻是我的中心:我不关心SignalR如何在里面工作! 因此,我没有替换DependencyResolver,而是创建了自己的IHubActivator实现:

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

答案 1 :(得分:24)

想要将其他答案扔到这里2美分,这有助于在SignalR中使用SimpleInjector或其他IoC找到自己的依赖注入方式。

使用@Steven's answer

如果您决定使用Steven的答案,请确保在撰写根之前注册中心路由。映射中心路由时,SignalRRouteExtensions.MapHubs扩展方法(又名routes.MapHubs())会在Register(Type, Func<object>)上调用GlobalHost.DependencyResolver,因此如果您将DefaultDependencyResolver换成Steven&# 39;在路由映射之前的SimpleInjectorResolver,您将遇到他的NotSupportedException

使用@Nathanael Marchand's answer

这是我的最爱。为什么呢?

  1. 代码少于SimpleInjectorDependencyResolver
  2. 无需替换DefaultDependencyResolver(a.k.a。GlobalHost.DependencyResolver),这意味着更少的代码。
  3. 您可以在映射中心路由之前或之后组成根,因为您没有替换DefaultDependencyResolver,它将&#34;只是工作&#34;。
  4. 就像Nathanael所说的那样,只有当你关心Hub类的依赖关系时才会这样,大多数人都可能会这样。如果你想把其他依赖注入SignalR,你可能想要接受史蒂文的回答。

    Hub

    中每个网络请求依赖项的问题

    有关SignalR的一个有趣的事情......当客户端与集线器断开连接时(例如关闭浏览器窗口),它将创建Hub类的新实例以便调用{{ 1}}。发生这种情况时, OnDisconnected()为空。因此,如果此HttpContext.Current具有按网络请求注册的任何依赖项,那么可能会出错

    在Ninject

    我尝试使用Ninject和ninject signalr dependency resolver on nuget进行SignalR依赖注入。使用此配置,在断开连接事件期间注入Hub时,将临时创建绑定.InRequestScope()的依赖项。由于Hub为空,我想Ninject只是决定忽略它并在不告诉你的情况下创建瞬态实例。也许有一个配置设置告诉ninject警告这个,但它不是默认值。

    在SimpleInjector中

    另一方面,当HttpContext.Current依赖于在Hub注册的实例时,

    SimpleInjector会抛出异常:

      

    NameOfYourHub类型的已注册委托引发了异常。该   类型NameOfYourPerRequestDependency的已注册委托引发了异常。该   YourProject.Namespace.NameOfYourPerRequestDependency注册为   &#39; PerWebRequest&#39;,但是在上下文之外请求实例   一个HttpContext(HttpContext.Current为null)。确保实例使用   应用程序初始化期间未解决此生活方式   阶段和在后台线程上运行时。用于解析实例   在后台线程上,尝试将此实例注册为&#39; Per Lifetime   范围&#39;:https://simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped

    ...请注意,只有WebRequestLifestlyle HttpContext.Current == null时才会出现此异常,只有在我知道时,才会发生SignalR请求Hub实例以调用OnDisconnected()的情况。

    Hub

    中每个网络请求依赖项的解决方案

    请注意,这些都不是理想的,它们都取决于您的应用要求。

    在Ninject

    如果您需要非瞬态依赖项,请不要覆盖OnDisconnected()或使用类依赖项执行任何自定义操作。如果这样做,图中的每个依赖项将是一个单独的(瞬态)实例。

    在SimpleInjector中

    WebRequestLifestlyeLifestyle.TransientLifestyle.SingletonLifetimeScopeLifestyle之间需要hybrid lifestyle。当HttpContext.Current不为空时,依赖关系只会像您通常期望的那样生存。但是,当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个实例的实体时遇到异常。当注册为单身人士时,你会得到更多的一般例外(不要将DbContext注册为单身人士)。在我的情况下,我需要DbContext生活在特定的生命周期内,其中同一个实例可以在许多嵌套操作中重用,这意味着我需要LifetimeScopeLifestyle

    现在,如果您使用上面的混合代码和falseLifestyle: new LifetimeScopeLifestyle()行,则在执行自定义IHubActivator.Create方法时会出现另一个异常:

      

    NameOfYourHub类型的已注册委托引发了异常。该   NameOfYourLifetimeScopeDependency注册为&#39; LifetimeScope&#39;,   但是在生命范围的上下文之外请求实例。   确保首先调用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<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);
            }
        }
    }
    

    ...依赖于每个Web请求实例的装饰ICommandHandler<T>实例。有关所用模式的详细信息,请阅读thisthis

    注册示例

    container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), assemblies);
    
    container.RegisterSingleDecorator(
        typeof(ICommandHandler<>),
        typeof(CommandLifetimeScopeDecorator<>)
    );
    

答案 2 :(得分:5)

更新 此答案已针对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<object> GetServices(Type serviceType)
    {
        return this.container.GetAllInstances(serviceType);
    }

    public void Register(Type serviceType, IEnumerable<Func<object>> activators)
    {
        throw new NotSupportedException();
    }

    public void Register(Type serviceType, Func<object> activator)
    {
        throw new NotSupportedException();
    }

    public void Dispose()
    {
        this.defaultResolver.Dispose();
    }
}

不幸的是,DefaultDependencyResolver的设计存在问题。这就是为什么上面的实现不继承它,而是包装它。我在SignalR网站上创建了一个关于此的问题。你可以阅读它here。虽然设计师同意我的意见,但不幸的是1.0版本中没有修复该问题。

我希望这会有所帮助。

答案 3 :(得分:4)

从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<object> GetServices(Type serviceType)
    {
        return _container.GetAllInstances(serviceType)
            .Concat(base.GetServices(serviceType));
    }
}

您必须明确注入您的集线器:

container.Register<MessageHub>(() => new MessageHub(new EFUnitOfWork()));

此配置在高流量网站上正常运行,没有任何问题。

答案 4 :(得分:0)

以下对我有用。此外,在实例化依赖项解析程序之前,您需要在集线器类的容器中注册委托。

ex: container.Register<MyHub>(() =>
        {
            IMyInterface dependency = container.GetInstance<IMyInterface>();

            return new MyHub(dependency);
        });

public class SignalRDependencyResolver : DefaultDependencyResolver
{
    private Container _container;
    private HashSet<Type> _types = new HashSet<Type>();

    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<object> GetServices(Type serviceType)
    {
        return _types.Contains(serviceType) ? _container.GetAllInstances(serviceType) : base.GetServices(serviceType);
    }
}