如何从外到内绑定装饰器与Ninject

本文关键字:Ninject 绑定 | 更新日期: 2023-09-27 18:14:06

我使用与WhenInjectedInto<>的ninject绑定从内到外绑定装饰器。然而,从不同的入口点,我需要不同的功能,可能以不同的顺序运行,所以我想从外到内绑定装饰器链。Ninject可以做到吗?

我想要实现这个目标:

Bind<IFooService>().To<SimpleService>().WhenInjectedInto<FeatureAFooServiceDecorator>();
Bind<IFooService>().To<FeatureAFooServiceDecorator>().WhenInjectedInto<FeatureBFooServiceDecorator>();
Bind<IFooService>().To<FeatureBFooServiceDecorator>().WhenInjectedInto<EntryPoint1>();
Bind<IFooService>().To<SimpleService>().WhenInjectedInto<FeatureBFooServiceDecorator>();
Bind<IFooService>().To<FeatureBFooServiceDecorator>().WhenInjectedInto<EntryPoint2>();

但这是不正确的,因为FeatureBFooServiceDecorator不清楚它将被注入(FeatureAFooServiceDecoratorSimpleService)。

我想解决方案是让事情以另一种方式绑定,比如:

For<EntryPoint1>().Use<FeatureBFooServiceDecorator>().ThenUse<FeatureAFooServiceDecorator>().ThenUse<SimpleService>();
For<EntryPoint2>().Use<FeatureBFooServiceDecorator>().ThenUse<SimpleService>();
编辑:

要手动完成此操作,我会这样做:

var entryPoint1 = new EntryPoint1(new FeatureBFooServiceDecorator(new FeatureAFooServiceDecorator(new SimpleService)));
var entryPoint2 = new EntryPoint2(new FeatureBFooServiceDecorator(new SimpleService));

(当然我会避免更新,因为这些类每个都有更多的依赖关系,其中一些是InRequestScopeInNamedScope)

注意:对于上面的例子,假设存在以下类:

public interface IFooService {/*...*/}
public class SimpleService : IFooService {/*...*/}
public class FeatureAFooServiceDecorator : IFooService
{
    private readonly IFooService _innerFooService;
    public FeatureAFooServiceDecorator(IFooService fooService) {
        _innerFooService = fooService;
    }
}
public class FeatureBFooServiceDecorator : IFooService {/*...same as above...*/}
public class EntryPoint1{
    public EntryPoint1(IFooService fooService){/*...*/}
}

public class EntryPoint2{
    public EntryPoint2(IFooService fooService){/*...*/}
}

如何从外到内绑定装饰器与Ninject

我猜你想做的是

public class FeatureBFooService : IFooService
{
    public FeatureBFooService(IFooService service1, IFooService service2)
    { ...}
}
var service = new FeatureBFooService(new FeatureAFooService(), new SimpleService());

(当然你不想自己做new)。因此,您多次使用相同的接口,甚至对于相同的构造函数,但不仅希望不同的实例,而且希望将不同的类型(FeatureAFooService, SimpleService)注入FeatureBFooService的构造函数。

我认为有两种方法可以达到这个目的。但老实说,我应该提醒你,这看起来很复杂。通常这意味着设计不理想,你最好考虑如何以不同的方式解决问题。毕竟,当实现共享相同的接口时,它们不应该做相同的事情吗?将IFooService集合注入使用所有这些服务的类中是一回事,但是这个类本身又是一个IFooService似乎有点奇怪。但话虽如此,我相信人们会做出自己的选择——有时也会犯错——因为这是最好的学习方式。当然,我的假设也可能是错误的,你所追求的是最好的可实现的解决方案。

方案一:.ToConstructor()绑定

Bind<IFooService>().ToConstructor(ctorArg => new FeatureBFooService(ctorArg.Inject<FeatureAFooService>(), ctorArg.Inject<SimpleService>()));

对于每个构造函数参数,您可以定义应该注入的内容。这样你就不需要依赖于绑定,而且可以确定为给定的类型注入了什么。


方案二:[Named] binding

调整实现如下:

public const string FeatureAService = "FeatureA";
public const string SimpleService = "Simple";
public class FeatureBFooService : IFooService
{
    public FeatureBFooService(
               [Named(FeatureAService)]I FooService service1, 
               [Named(SimpleService] IFooService service2)
    { ...}
}
Bind<IFooService>().To<FeatureAService>().Named(FeatureAService);
Bind<IFooService>().To<SimpleService>().Named(SimpleService);

方案3:.ToProvider()绑定+自定义绑定逻辑

你还可以做的是

Bind<IFooService>().ToProvider<FooServiceProvider>();

其中FooServiceProvider将根据您的自定义逻辑决定要实例化的确切依赖项。它可以选择

IResolutionRoot.Get<FeatureAFooService>();

或者您仍然可以使用[Named]特性:

IResolutionRoot.Get<IFooService>(FeatureAService);
例如,它可以看起来像(伪代码):
public class FooServiceProvider : Provider<IFooService>
{
    protected override IFooService CreateInstance(IContext context)
    {
       Type returnType = DetermineImplementationType(context);
       switch(returnType)
       {
           case typeof(FeatureBFooService):
               return CreateFeatureBFooService(context);
               break:
           default:
               throw new NotSupportedException(...);
       }
    }
    private static Type DetermineImplementationType(IContext context)
    {
       // your custom logic here
    }
    private static IFooService CreateFeatureBFooService(IContext context)
    {
        var dependency1 = context.Kernel.Get<IFooService>(FeatureAFooService);
        var dependency2 = context.Kernel.Get<IFooService>(SimpleService);
        return context.Kernel.Get<IFooService>(
                   FeatureBFooService,
                   new ConstructorArgument("service1", dependency1),
                   new ConstructorArgument("service2", dependency2));
    }
}

请注意,对于ConstructorArgument,该值被注入到与名称(service1, service2)匹配的构造函数参数中,因此这是一个重构陷阱。另外请注意,如果需要保留上下文,也可以使用IContext.Kernel.ContextPreservingGet<>。但是,这只能使用扩展名ninject.extensions.ContextPreservation.