【问题标题】:Using Simple Injector with SignalR使用 SignalR 的简单注入器
【发布时间】:2012-05-20 07:29:02
【问题描述】:

我认为使用我自己的 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 和 Simple Injector newb,所以我想先问一下。 :)

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

【问题讨论】:

    标签: c# signalr simple-injector


    【解决方案1】:

    嗯,我昨天试过了,我找到了解决办法。 据我说,我想要在 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)。
    • 我对这个问题进行了很多次搜索,但不知何故错过了这个答案,直到我已经在其他地方找到了它。更多赞成票!
    • 更简单的实现,将其传递给 MVC 依赖解析器:return (IHub)DependencyResolver.Current.GetService(descriptor.HubType);
    • 请问你叫什么RouteTable.Routes.MapHubs();?这条线我不明白。如果我省略它,它也可以作为一种魅力。
    • GlobalHost 在 AspNetCore 3 中不再存在
    【解决方案2】:

    想把我的 2 美分和其他答案一起扔在这里,这有助于在 SignalR 中找到自己的依赖注入方式,无论是使用 SimpleInjector 还是其他 IoC。

    使用@Steven's answer

    如果您决定使用 Steven 的答案,请确保在编写根之前注册您的集线器路由。 SignalRRouteExtensions.MapHubs 扩展方法(又名routes.MapHubs())将在映射枢纽路线时在GlobalHost.DependencyResolver 上调用Register(Type, Func&lt;object&gt;),因此如果在映射路线之前将DefaultDependencyResolver 替换为Steven 的SimpleInjectorResolver,您将碰到他的NotSupportedException

    使用@Nathanael Marchand's answer

    这是我的最爱。为什么?

    1. 代码少于SimpleInjectorDependencyResolver
    2. 无需替换DefaultDependencyResolver(又名GlobalHost.DependencyResolver),这意味着更少的代码。
    3. 您可以在映射集线器路由之前或之后组成根,因为您没有替换DefaultDependencyResolver,它会“正常工作”。

    就像 Nathanael 说的那样,这只是在你关心 Hub 类的依赖关系的情况下,这可能是大多数人的情况。如果您想将其他依赖项注入 SignalR 中,您可能想要使用 Steven 的答案。

    Hub 中每个 Web 请求的依赖关系问题

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

    在忍者中

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

    在 SimpleInjector 中

    另一方面,当 Hub 依赖于向 WebRequestLifestlyle 注册的实例时,SimpleInjector 将引发异常:

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

    ...请注意,此异常只会在 HttpContext.Current == null 时冒泡,据我所知,仅当 SignalR 请求 Hub 实例以调用 OnDisconnected() 时才会发生。

    Hub 中每个 Web 请求依赖项的解决方案

    请注意,这些都不是真正理想的,这完全取决于您的应用程序要求。

    在忍者中

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

    在 SimpleInjector 中

    您需要一个介于 WebRequestLifestlyeLifestyle.TransientLifestyle.SingletonLifetimeScopeLifestyle 之间的 hybrid lifestyle。当HttpContext.Current 不为空时,依赖项将仅与您通常期望的 Web 请求一样长。但是,当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

    就我而言,我有一个 EntityFramework DbContext 依赖项。这些可能很棘手,因为它们可能会在临时注册或作为单例注册时暴露问题。临时注册时,您可能会在尝试使用附加到 2 个或更多 DbContext 实例的实体时遇到异常。当注册为单例时,您最终会遇到更一般的异常(永远不要将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 的帮助下,我最终得到了一个命令装饰器(和一个查询装饰器)。 Hub 不能依赖于 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&lt;T&gt; 实例依赖于每个网络请求的实例。有关所用模式的更多信息,请阅读thisthis

    示例注册

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

    【讨论】:

    • 说实话很有意思,你如何使用 handlerFactory 注册你的依赖?
    • 你不需要做任何特别的事情来注册依赖来使用 handlerFactory。我只是用RegisterManyForOpenGeneric 注册命令处理程序,然后用RegisterDecorator 装饰它们并传递CommandLifetimeScopeDecorator。您实际上可以将上面的代码更改为使用ICommandHandler&lt;TCommand&gt; 而不是Func&lt;ICommandHandler&lt;TCommand&gt;&gt;,它仍然会注入。它只会注入实例而不是委托来延迟注入实例。我已经用一个例子更新了答案。
    • 我如何将你的生命周期范围实现到集线器中?
    • 所有信息都在这里,或者您可以自己查找。如果主题太高级,请尝试其他解决方案或提出其他问题。
    • 没问题,我会四处寻找,我会努力解决的,谢谢
    【解决方案3】:

    更新此答案已针对 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 版本中并没有得到修复。

    我希望这会有所帮助。

    【讨论】:

    • 查看此链接:github.com/SignalR/SignalR/blob/master/SignalR/Infrastructure/…。当我使用你的代码时,我仍然遇到同样的错误,我认为我可能需要重写的是注册并调用基本构造函数而不是 GetServices。我要试一试。
    • @Steven:正确,我认为 DefaultDependencyResolver 也有缺陷。我几乎尝试了所有方法,但仍然无法注入依赖项。
    • @AmrEllafy:我的问题被开发人员关闭为重复,但总的来说他同意我的推理。是否还没有解决此问题的修复程序或更新?
    • @Steven:我最终直接注入了我的依赖项 DependencyResolver.Current.GetService();我找不到更清洁的方法,至少在 Spring.Net 中是这样
    【解决方案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()));
    

    此配置在高流量网站上实时运行,没有问题。

    【讨论】:

    • GetService() 主体的简短版本是:return ((IServiceProvider)container).GetService(serviceType) ?? base.GetService(serviceType);
    • GetServices() 主体的简短版本是:return container.GetAllInstances(serviceType).Concat(base.GetServices(serviceType));
    • 我根据@blueling 的反馈更新了答案。顺便说一句,GetServices 方法中有一个错误。代码检查了GetRegistration(serviceType, false) != null,但这个谓词通常会产生错误,因为集合的注册是不同的注册。但是,执行GetRegistration(typeof(IEnumerable&lt;&gt;).MakeGenericType(serviceType), false) != null 将不起作用,因为它永远不会返回 null(因为 Simple Injector 会在它丢失时为您进行注册)。所以blueling的回答要好得多。
    • 你在Startup 类中从哪里获得Container 的实例?
    • 我很困惑:如果您仍然需要显式地注入对集线器注册的依赖项,那么这一切的意义何在?
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      .NET Core 3.x 中,signalR 现在是一个瞬态。您可以使用 DI 注入集线器。所以你开始映射集线器,实现接口和集线器,并通过 DI 访问它的上下文。

      启动:

      app.UseSignalR(routes =>
      {
          routes.MapHub<YourHub>(NotificationsRoute); // defined as string
      });
      
      services.AddSignalR(hubOptions =>
      {
          // your options
      })
      

      然后你实现一个像这样的接口:

      public interface IYourHub
      {
          // Your interface implementation
      }
      

      还有你的中心:

      public class YourHub : Hub<IYourHub>
      {
          // your hub implementation
      }
      

      最后你像这样注入集线器:

      private IHubContext<YourHub, IYourHub> YourHub
      {
          get
          {
              return this.serviceProvider.GetRequiredService<IHubContext<YourHub, IYourHub>>();
          }
      }
      

      您还可以为您的集线器(中间件)定义一个服务,而不是将上下文直接注入您的类。

      假设你在接口中定义了方法Message,所以在你的类中你可以这样发送消息:

      await this.YourHub.Clients.Group("someGroup").Message("Some Message").ConfigureAwait(false);
      

      如果没有在你刚刚使用的接口中实现:

      await this.YourHub.Clients.Group("someGroup").SendAsync("Method Name", "Some Message").ConfigureAwait(false);
      

      【讨论】:

      最近更新 更多