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

StackOverflow https://stackoverflow.com/questions/719346

Вопрос

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

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 для создания зависимости таким образом?

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

Решение

Самым простым способом было бы создать 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, а не Parameter .Обновлено для отражения.

Этот код Печатает:"Вызван из 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);
    }
}

Редактировать:

Другой метод, который может быть полезен, - это использовать привязку Bind().To().WithConstructorArgument() , которая может быть полезна при использовании либо с самосвязыванием, либо с a .Условие 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; }    
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top