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

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

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

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

Если вы не уверены, почему мне нужна такая странная структура, подумайте, как лучше всего работает 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 ....  */
}

3 ответа

Решение

Самый простой способ - создать 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) { ... }
}

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

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

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

РЕДАКТИРОВАТЬ:

Похоже, это будет работать так:

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

Тогда у вас есть свой ILog

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

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

РЕДАКТИРОВАТЬ:

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

Этот код печатает: "Называется из Инициативы"

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

РЕДАКТИРОВАТЬ:

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

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

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; }    
Другие вопросы по тегам