Использование 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; }