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