我有一个抽象工厂,它创建一些由IService
接口表示的服务。在工厂中,我有两个Create
方法,因为在其中一个方法中,我允许使用者传递现有的IServiceLogger
实例以供构造的服务树使用。
public interface IMyServiceFactory {
IMyService Create(IServiceLogger loggerInstance);
IMyService Create();
}
因为应该在服务树之间共享IServiceLogger
,所以在将它绑定到具体实现时我会使用InCallScope
。
如何使用Ninject实现此方案?我尝试了以下方法。
1。手动创建工厂实施
internal class MyServiceFactory : IMyServiceFactory {
private IResolutionRoot _kernel;
public MyServiceFactory
public IMyService Create(IServiceLogger loggerInstance) {
// what should go here? how can I pass the existing instance to Ninject Get method and make Ninject to use it for the whole resolution tree, just as it were created by Ninject and used as InCallScope?
}
// this one is trivial...
pulbic IMyService Create() {
return _kernel.Get<IMyService>();
}
}
更新
实际上,我发现了一种混乱且不太安全的方法。我可以通过GetBindings
,然后Rebind
IServiceLogger
ToConstant
,然后Get
IMyService
实例获取当前绑定,最后恢复原始绑定与AddBinding
。我不喜欢它,它感觉很臭,更糟糕的是,它不是线程安全的,因为另一个线程可以在此代码的中间请求IMyService
因此使用本地临时绑定。
2。使用Ninject.Extensions.Factory
只使用ToFactory
绑定,但这不起作用,因为它只是尝试将参数用作简单的构造函数参数(如果适用),而不是整个解析树的对象
答案 0 :(得分:0)
我会更多地控制Ninject的内核,而不是为工厂创建一个类。 并在Ninject中使用Func绑定,如下所示:
Bind<Func<IMyService>>().ToMethod(s => CreateService);
通过绑定ILoggerService或不绑定它,您可以集中控制您的服务中是否有记录器。(只需将其注释掉)
Bootstrapper的实现:
public class Bootstrapper
{
private IKernel _kernel = new StandardKernel();
public Bootstrapper()
{
_kernel.Bind<MyStuff>().ToSelf();
_kernel.Bind<IServiceLogger>().To<ServiceLogger>();
_kernel.Bind<IMyService>().To<MyService>();
_kernel.Bind<Func<IMyService>>().ToMethod(s => CreateService);
}
public IKernel Kernel
{
get
{
return _kernel;
}
set
{
_kernel = value;
}
}
private IMyService CreateService()
{
if(_kernel.GetBindings(typeof(IServiceLogger)).Any())
{
return _kernel.Get<IMyService>(new ConstructorArgument("logger", _kernel.Get<IServiceLogger>()));
}
return _kernel.Get<IMyService>();
}
}
为工厂实施消费者类:
internal class MyStuff
{
private readonly Func<IMyService> _myServiceFactory;
public MyStuff(Func<IMyService> myServiceFactory)
{
_myServiceFactory = myServiceFactory;
_myServiceFactory.Invoke();
}
}
MyService的简单实现:
internal class MyService
:IMyService
{
public MyService()
{
Console.WriteLine("with no parameters");
}
public MyService(IServiceLogger logger)
{
Console.WriteLine("with logger parameters");
}
}
简单 ServiceLogger :
internal class ServiceLogger
:IServiceLogger
{
public ServiceLogger()
{
}
}
internal interface IServiceLogger
{
}
答案 1 :(得分:0)
重要更新
虽然我的原始答案给了我一个可行的解决方案,但是偶然的InteliSense导航我发现有一个内置工具可以解决这个问题。我只需要使用内置的TypeMatchingArgumentInheritanceInstanceProvider
来执行此操作,甚至更多,因为由于参数类型匹配,不再需要命名约定。
最好有一个关于这些选项的更详细的文档,或者也许只是我当前无法找到它的人。
原始回答
我尝试了几种方法,最后采用了一种与Ninject的上下文参数继承略有不同的基于约定的方法。
约定用于通过依赖关系树命名的构造函数参数。例如,每当将IServiceLogger
实例注入服务类时,该参数应该被称为serviceLogger
。
考虑到上述惯例,我测试了以下方法。首先,我为工厂扩展实现了一个自定义实例提供程序。此自定义提供程序会覆盖为上下文创建构造函数参数的机制,以便开发人员指定应将其设置为已继承的多个命名参数。这样,具有指定名称的所有参数将在get操作期间通过整个请求图继承。
public class ParameterInheritingInstanceProvider : StandardInstanceProvider
{
private readonly List<string> _parametersToInherit = new List<string>();
public ParameterInheritingInstanceProvider(params string[] parametersToInherit)
{
_parametersToInherit.AddRange(parametersToInherit);
}
protected override IConstructorArgument[] GetConstructorArguments(MethodInfo methodInfo, object[] arguments)
{
var parameters = methodInfo.GetParameters();
var constructorArgumentArray = new IConstructorArgument[parameters.Length];
for (var i = 0; i < parameters.Length; ++i)
constructorArgumentArray[i] = new ConstructorArgument(parameters[i].Name, arguments[i], _parametersToInherit.Contains(parameters[i].Name));
return constructorArgumentArray;
}
}
然后在绑定配置后,我只是将其与相应的参数名称一起放入。
kernel.Bind<IMyServiceFactory>().ToFactory(() => new ParameterInheritingInstanceProvider("serviceLogger"));
最后,我查看了参数命名,例如,将工厂界面中的loggerInstance
更改为serviceLogger
以符合约定。
这个解决方案仍然不是最好的解决方案,因为它有一些限制。
答案 2 :(得分:0)
我意识到这是很久以前问过的,但是我一直想自己做同样的事情,最后发现可以使用传递给IParameter
方法的Get()
数组来指定ContructorArgument
仅可用于当前的Get()
呼叫。这允许我在创建Hangfire作业时使用特定的构造函数值,从而允许Hangfire作业在需要时在每次调用时使用不同的数据库连接。
EnvironmentName forcedEnv = new EnvironmentName() { Name = dbName };
// For this instantiation, set the 'envName' parameter to be the one we've specified for this job
var instance = ResolutionExtensions.Get((IResolutionRoot) _kernel, jobType,
new IParameter[] {new ConstructorArgument("envName", forcedEnv, true)});
return instance;
通过将shouldInherit
的值设置为true
,可以确保该值沿解析链传递。因此,它将传递给使用该参数的依赖关系树中的任何对象(但仅用于此特定实例化)。