35

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

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 新手,所以我想先问一下。:)

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

4

6 回答 6

51

嗯,我昨天试过了,我找到了解决办法。据我说,我想要在 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();
于 2013-04-02T21:46:30.000 回答
26

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

使用@Steven 的回答

如果您决定使用 Steven 的答案,请确保在编写根之前注册您的集线器路由。SignalRRouteExtensions.MapHubs扩展方法(aka routes.MapHubs())将在映射中心路由时调用,Register(Type, Func<object>)因此GlobalHost.DependencyResolver如果您在映射路由之前将DefaultDependencyResolver与 Steven's交换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有任何按网络请求注册的依赖项,则可能会出错

在忍者

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

在 SimpleInjector 中

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

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

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

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

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

在忍者

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

在 SimpleInjector 中

您需要一种介于、或之间的混合生活方式。当不为 null 时,依赖项将仅与您通常期望的 Web 请求一样长。但是,当为 null 时,依赖项将被临时注入,作为单例,或在生命周期范围内。WebRequestLifestlyeLifestyle.TransientLifestyle.SingletonLifetimeScopeLifestyleHttpContext.CurrentHttpContext.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

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

示例注册

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

container.RegisterSingleDecorator(
    typeof(ICommandHandler<>),
    typeof(CommandLifetimeScopeDecorator<>)
);
于 2013-08-28T12:23:05.237 回答
6

更新此答案已针对 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();
    }
}

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

我希望这有帮助。

于 2012-05-11T21:56:18.320 回答
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()));

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

于 2013-08-30T15:58:53.703 回答
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);
    }
}
于 2013-03-29T17:32:33.647 回答
0

.NET Core 3.xsignalR 现在是一个瞬态。您可以使用 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);
于 2020-02-17T11:46:28.533 回答