Использование Ninject (или другого контейнера) Как узнать тип, запрашивающий услугу?

Предположим, что у меня есть интерфейс для службы:

public interface IFooService
{
   void DoSomething();
}

И конкретная реализация этой службы, которая является общей:

public class FooService<TRequestingClass> : IFooService
{
   public virtual void DoSomething() { }
}

И у меня есть другой класс, которому нужен экземпляр IFooService:

public class Bar
{
   private IFooService _fooService;
   public Bar(IFooService fooService)
   {
      this._fooService = fooService;
   }
}

Мне нужно подключить мой контейнер IoC таким образом, чтобы при создании Bar он передавал аргумент конструктора FooService <Bar> . Есть много других классов, как Bar. Каждому может также понадобиться экземпляр FooService <TRequestingClass> переданный им, где TRequestingClass - тип класса, которому нужен экземпляр IFooService. Мне не нужно подвергать эту причудливость потребителям IFooService. Все, о чем они должны заботиться, это то, что они могут вызывать методы IFooService, которые они передали. Им не нужно знать, что конкретная реализация IFooService, которую они передали, требует чего-то особенного, чтобы быть построенным.

Приемлемая альтернатива FooService <T> будет неродским классом, который имеет строковый аргумент в своем конструкторе, который содержит имя класса, для которого он создается. то есть:

public class FooService : IFooService
{
   public FooService(string requestingClassName) { }
}

Как подключить мой контейнер IoC для создания зависимости таким образом?

Если вы смущены, почему бы мне захотеть такой wierd-структуры, подумайте, как лучше работает log4net, когда вы получаете ILog, который создается с помощью log4net.LogManager.GetLogger(typeof (SomeClass)). Я не хочу заманивать свой код ссылками на log4net, поэтому я бы хотел написать простой интерфейс ILogger и реализовать его с чем-то вроде этого:

public class GenericLogger<T> : ILogger
{
    private readonly ILog log;

    public GenericLogger()
    {
        this.log = log4net.LogManager.GetLogger(typeof(T));
    }

    public void Debug(object message)
    {
        this.log.Debug(message);
    }

    /* .... etc ....  */
}

Ответ 1

Самый простой способ - создать интерфейс ILogger<T>:

public class ILogger<T> : ILogger { }
public class GenericLogger<T> : ILogger<T> { ... }

Затем полагайтесь на общий вывод типа, чтобы получить правильный тип. Например, в Ninject вам понадобится следующее связывание:

Bind(typeof(ILogger<>)).To(typeof(GenericLogger<>));

Тогда ваши типы потребления будут выглядеть следующим образом:

public class FooService : IFooService {
  public FooService(ILogger<FooService> logger) { ... }
}

Если вы категорически против интерфейса ILogger<T>, вы можете сделать что-то более творческое, как пользовательский поставщик, который читает IContext для определения родительского типа.

public class GenericLogger : ILogger {
  public class GenericLogger(Type type) { ... }
}

public class LoggerProvider : Provider<ILogger> {
  public override ILogger CreateInstance(IContext context) {
    return new GenericLogger(context.Target.Member.ReflectedType);
  }
}

Тогда используемые типы будут работать следующим образом:

public class FooService : IFooService {
  public FooService(ILogger logger) { ... }
}

Ответ 2

Если я не недопонимаю вас, почему бы просто не заставить GericLogger Constructor использовать параметр, являющийся типом объекта. Затем сделайте следующее:

ILog = kernel.Get<ILog>(ParameterList);

Я еще не полностью прочитал список Ninject Parameter Lists, но, похоже, это способ ввода типа параметра с помощью IParameterList.

EDIT:

Похоже, это будет работать следующим образом:

ILog = kernel.Get<ILog>(new ConstructorArgument[] { 
    new ConstructorArgument("ClassName", this.GetType().Name) 
})

Затем у вас есть ваш ILog

class GenericLogger : Ilog
{
    GenericLogger(string ClassName) {};
}

Я не тестировал это, просто то, что кажется от источника Ninject (я смотрю на недавнее дерево Ninject2)

EDIT:

Вы хотите передать ConstructorArgument, а не параметр. Обновлено, чтобы отразить.

Этот код Печатает: "Called From Init"

class Init {
    public void Run() {
        StandardKernel kernel = new StandardKernel( new MyLoader() );
        kernel.Get<Tester>( new ConstructorArgument[] { 
            new ConstructorArgument( "ClassName", 
                this.GetType().Name 
            ) 
        } );            
    }
}

public class Tester {
    public Tester(string ClassName) {
        Console.WriteLine("Called From {0}", ClassName);
    }
}

EDIT:

Другим методом, который может быть полезен, является использование привязки Bind(). To(). WithConstructorArgument(), которое может быть полезно при использовании либо с самосвязкой, либо с условием .InInjectedInto().

Ответ 3

Чтобы подробнее разобраться с идеей пользовательского провайдера, простейший способ сделать это...

internal class LogModule : StandardModule
    {
        private class log4netILogProvider : SimpleProvider<log4net.ILog>
        {
            protected override ILog CreateInstance(IContext context)
            {
                return LogManager.GetLogger(context.Instance.GetType());
            }
        }

        public override void Load()
        {
            Bind<log4net.ILog>().ToProvider(new log4netILogProvider());
        }
    }

Затем в классы, которые вводятся:

[Inject]
        public ILog logger { get; set; }