使用SimpleInjector和SignalR

rba*_*all 27 c# simple-injector signalr

我认为使用我自己的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;
}
Run Code Online (Sandbox Code Playgroud)

然后我的班级:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
    ConstructorSelector.MostParameters);
Run Code Online (Sandbox Code Playgroud)

但是还有其他一些人!我到达:

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);
Run Code Online (Sandbox Code Playgroud)

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

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

Nat*_*and 45

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以像这样注册(在Application_Start中):

var activator = new SimpleInjectorHubActivator(container);
GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
RouteTable.Routes.MapHubs();
Run Code Online (Sandbox Code Playgroud)

  • 我多次搜索这个问题,不知何故错过了这个答案,直到我已经在其他地方找到了它。更多投票! (2认同)
  • 甚至更简单的实现,将其传递给MVC Dependency Resolver:`return(IHub)DependencyResolver.Current.GetService(descriptor.HubType);` (2认同)

dan*_*wig 25

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

使用@Steven的答案

如果您决定使用Steven的答案,请确保在撰写根目录之前注册中心路由.该SignalRRouteExtensions.MapHubs扩展方法(又名routes.MapHubs())将调用Register(Type, Func<object>)GlobalHost.DependencyResolver映射枢纽路由时,如果你换出DefaultDependencyResolver与史蒂芬的SimpleInjectorResolver路由映射前,你会碰上他NotSupportedException.

使用@Nathanael Marchand的回答

这是我最喜欢的.为什么?

  1. 代码少了SimpleInjectorDependencyResolver.
  2. 无需替换DefaultDependencyResolver(aka GlobalHost.DependencyResolver),这意味着更少的代码.
  3. 您可以在映射中心路由之前或之后组成根,因为您没有替换DefaultDependencyResolver它,它将"正常工作".

就像Nathanael所说的那样,只有当你关心你的Hub类的依赖关系时才会这样,大多数人都可能会这样.如果你想把其他依赖注入SignalR,你可能想要接受Steven的回答.

每个Web请求依赖项的问题 Hub

SignalR有一个有趣的事情......当客户端与集线器断开连接时(例如关闭浏览器窗口),它将创建一个新的Hub类实例以便调用OnDisconnected().发生这种情况时,HttpContext.Current为null.因此,如果这Hub具有根据web请求注册的任何依赖项,则可能会出现问题.

在Ninject

我尝试使用Ninject和nuget上ninject signalr依赖解析器进行SignalR依赖注入.使用此配置,在断开事件期间.InRequestScope()注入到a时,将临时创建绑定的依赖Hub项.既然HttpContext.Current是null,我想Ninject只是决定忽略它并在不告诉你的情况下创建瞬态实例.也许有一个配置设置告诉ninject警告这个,但它不是默认值.

在SimpleInjector中

另一方面,当一个Hub依赖于注册的实例时,SimpleInjector将抛出异常WebRequestLifestlyle:

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

...注意HttpContext.Current == null,只有当我想知道SignalR请求一个Hub实例才能调用时,这个异常才会冒泡OnDisconnected().

针对每个Web请求依赖关系的解决方案 Hub

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

在Ninject

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

在SimpleInjector中

你需要一个混合的生活方式之间WebRequestLifestlye,要么Lifestyle.Transient,Lifestyle.SingletonLifetimeScopeLifestyle.如果HttpContext.Current不为null,则依赖关系将仅与您通常期望的Web请求一样长.但是,当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()
);
Run Code Online (Sandbox Code Playgroud)

更多关于 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
}
Run Code Online (Sandbox Code Playgroud)

必须在此using块中解析使用生命周期范围注册的任何依赖项.此外,如果这些依赖项中的任何一个实现IDisposable,它们将在using块的末尾被处理掉.不要试图做这样的事情:

public IHub Create(HubDescriptor descriptor)
{
    if (HttpContext.Current == null)
        _container.BeginLifetimeScope();
    return _container.GetInstance(descriptor.HubType) as IHub;
}
Run Code Online (Sandbox Code Playgroud)

我问过史蒂文(如果你不知道的话,他也恰好是SimpleInjector的作者),他说:

那么..如果你不处理LifetimeScope,你将遇到大麻烦,所以要确保它们被处理掉.如果你不处理范围,它们将在ASP.NET中永远存在.这是因为范围可以嵌套并引用其父范围.因此,一个线程保持最活跃的内部范围(使用其缓存),并且此范围保持其父范围(具有其缓存)等等.ASP.NET池线程并且在从池中获取线程时不会重置所有值,因此这意味着所有范围都保持活动状态,并且下次从池中获取线程并启动新的生命周期范围时,您将只是创建一个新的嵌套范围,这将继续堆叠.迟早,你会得到一个OutOfMemoryException.

您不能使用它IHubActivator来限定依赖项的范围,因为它不会像Hub它创建的实例那样存在.因此,即使您将BeginLifetimeScope()方法包装在一个using块中,您的依赖项也会在Hub创建实例后立即处理.你真正需要的是另一层间接.

我非常感谢Steven的帮助,是一个命令装饰器(以及一个查询装饰器).A 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

...它是ICommandHandler<T>依赖于每个Web请求实例的修饰实例.有关所用模式的更多信息,请阅读本文此内容.

注册示例

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

container.RegisterSingleDecorator(
    typeof(ICommandHandler<>),
    typeof(CommandLifetimeScopeDecorator<>)
);
Run Code Online (Sandbox Code Playgroud)


Ste*_*ven 5

更新此答案已针对SignalR 1.0版进行了更新

这是如何IDependencyResolver为Simple Injector 构建SignalR :

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我希望这有帮助.