2016-02-17 8 views
0

У меня есть абстрактный завод, который создает некоторую услугу, представленную интерфейсом IService. На заводе у меня есть два метода Create, потому что на одном из них я разрешаю потребителю передать существующий экземпляр IServiceLogger, который будет использоваться построенным деревом службы.Передайте экземпляр зависимостей параметру фабричного метода, чтобы сделать Ninject используемым в разрешении

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>(); 
    } 
} 

UPDATE

На самом деле я нашел грязный и не слишком безопасный способ для этого. Я могу получить текущие привязки через GetBindings, затем RebindIServiceLoggerToConstant, затем Get экземпляр IMyService и, наконец, восстановить оригинальные привязки с помощью AddBinding. Мне это не нравится, оно кажется вонючим и, что еще хуже, оно не является потокобезопасным, потому что другой поток может запросить IMyService в середине этого кода и, следовательно, использовать локальную временную привязку.

2. Использование Ninject.Extensions.Factory

Просто используйте ToFactory связывания, но это не работает, потому что он просто пытается использовать параметр как простой аргумент конструктора (если применимо), а не как объект для всего дерева разрешений.

ответ

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")); 

Наконец я рассмотрел параметр именование, и exampled изменил loggerInstance в интерфейсе фабрики для serviceLogger, чтобы соответствовать соглашению.

Это решение по-прежнему не самое приятное, так как оно имеет несколько ограничений.

  1. Это склонность к ошибкам. Можно сделать ошибки, которые трудно отслеживать, не сохраняя соглашение об именах, потому что в настоящее время он молча проваливается, если соглашение не соответствует. Вероятно, это может быть улучшено, я подумаю об этом позже.
  2. Он обрабатывает только инъекцию конструктора, однако это не должно быть большой проблемой, так как это предлагаемая техника. Например, я почти никогда не делаю других инъекций.
0

Я бы дал больше контроля над ядром Ninject и вообще не создавал класс для фабрики. И использовать Func связывание в Ninject так:

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 
    { 
    } 
+0

Благодарим за ответ. То, что вы описали, довольно простое и даже не нуждается в коде IMO, поскольку, поскольку простые одноуровневые аргументы конструктора идут, расширение Factory выполняет задание (как я упоминал в своем сообщении). Проблема возникает, когда 1: регистратор МОЖЕТ быть дополнительным. 2. Регистратор должен использоваться полным деревом службы, а не только на верхнем уровне. Однако я нашел решение, скоро опубликую его. –

+0

О, хорошо. Тогда я бы рекомендовал использовать условное связывание, которое довольно сильно в ninject. –