【问题标题】:Pass a dependency instance to a factory method parameter to make Ninject use it within the resolution将依赖实例传递给工厂方法参数以使 Ninject 在解析中使用它
【发布时间】:2016-02-17 11:35:50
【问题描述】:

我有一个抽象工厂,它创建一些由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获取当前绑定,然后RebindIServiceLoggerToConstant,然后GetIMyService实例,最后用AddBinding恢复原始绑定。我不喜欢它,感觉很臭,更糟糕的是,它不是线程安全的,因为另一个线程可以在这段代码中间请求IMyService,因此使用本地临时绑定。

2。使用 Ninject.Extensions.Factory

只需使用ToFactory 绑定,但这不起作用,因为它只是尝试将参数用作简单的构造函数参数(如果适用),而不是作为整个解析树的对象。

【问题讨论】:

    标签: c# ninject ninject-extensions


    【解决方案1】:

    我会给 Ninject 的内核更多的控制权,并且根本不为工厂创建一个类。 并像这样在 Ninject 中使用 Func 绑定:

    Bind<Func<IMyService>>().ToMethod(s => CreateService);
    

    通过绑定 ILoggerService 或不绑定 this,您可以集中控制您的服务中是否有记录器。(尝试将其注释掉)

    这里是 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
        {
        }
    

    【讨论】:

    • 感谢您的回答。您所描述的内容非常简单,甚至不需要任何代码 IMO,因为就简单的一级构造函数参数而言,Factory 扩展可以完成这项工作(正如我在帖子中提到的那样)。问题出现在 1:记录器可能是可选的 2. 记录器应该由完整的服务树使用,而不仅仅是在顶层。不过我找到了解决方案,我会尽快发布。
    • 哦,好的。然后我会推荐使用条件绑定,这在ninject中非常强大。
    【解决方案2】:

    重要更新

    虽然我最初的回答给了我一个可行的解决方案,但通过意外的 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 以符合约定。

    这个解决方案仍然不是最好的,因为它有几个限制。

    1. 容易出错。如果不遵守命名约定,可能会产生难以跟踪的错误,因为目前如果约定不匹配,它会默默地失败。这可能会有所改进,我稍后会考虑。
    2. 它只处理构造函数注入,但这不应该是一个大问题,因为这是建议的技术。例如,我几乎从不进行其他类型的注射。

    【讨论】:

      【解决方案3】:

      我意识到很久以前有人问过这个问题,但我自己也想做同样的事情,最后发现您可以使用传递给Get() 方法的IParameter 数组来指定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,您可以确保该值沿解析链传递。所以它会被传递给依赖树中使用该参数的任何对象(但仅限于这个特定的实例化)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-08-29
        • 1970-01-01
        • 2020-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        相关资源
        最近更新 更多