Использование Ninject (или другого контейнера) Как я могу узнать тип, который запрашивает службу? - PullRequest
3 голосов
/ 05 апреля 2009

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

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 ]

6 голосов
/ 05 апреля 2009

Самый простой способ - создать интерфейс 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) { ... }
}
1 голос
/ 05 апреля 2009

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

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

Я еще не полностью прочитал списки параметров Ninject, но, похоже, это способ ввести тип параметра с помощью 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 (), которая может быть полезна при использовании либо с самосвязыванием, либо с условием .WhenInjectedInto ().

0 голосов
/ 05 августа 2009

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

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