Usando Ninject (ou algum outro recipiente) Como posso descobrir o tipo que está solicitando o serviço?

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

Pergunta

Suponha que eu tenho uma interface para um serviço:

public interface IFooService
{
   void DoSomething();
}

E uma implementação concreta de que o serviço que é um genérico:

public class FooService<TRequestingClass> : IFooService
{
   public virtual void DoSomething() { }
}

E eu tenho alguma outra classe que precisa de uma instância de IFooService:

public class Bar
{
   private IFooService _fooService;
   public Bar(IFooService fooService)
   {
      this._fooService = fooService;
   }
}

Eu preciso conectar-se meu contêiner IoC tal que quando Bar é criado, ele é passado um argumento construtor da FooService . Há muitas outras classes apenas como Bar. Cada poder também precisa de uma instância de FooService passado para eles, onde TRequestingClass é o tipo da classe que precisa da instância de IFooService. Eu não preciso para expor essa estranheza para os consumidores de IFooService. Todos eles se preocupam com é que eles podem chamar os métodos do IFooService eles foram passados. Eles não devem precisa saber que a implementação concreta de IFooService eles foram passados ??necessária especial algo a ser construído.

Um alternatative aceitável para FooService iria ser uma classe não-genérico que tem um argumento de cadeia em seu constructur que contém o nome da classe que está sendo criado. ou seja:

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

Como posso ligar o meu contêiner IoC para construir uma dependência desta maneira?

Se você está confuso por que eu iria querer uma estrutura deste tipo estranho, considere como log4net funciona melhor quando você começa uma ILOG que é criada com log4net.LogManager.GetLogger (typeof (SomeClass)). Eu não quero lixo meu código com referências a log4net, então eu gostaria de escrever uma interface ILogger simples, e implementá-lo com algo parecido com isto:

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 ....  */
}
Foi útil?

Solução

A maneira mais fácil seria criar uma interface ILogger<T>:

public class ILogger<T> : ILogger { }
public class GenericLogger<T> : ILogger<T> { ... }

Em seguida, dependem de inferência de tipo genérico para obter o tipo correto. Por exemplo, em Ninject, a seguinte ligação é tudo o que você precisa:

Bind(typeof(ILogger<>)).To(typeof(GenericLogger<>));

Em seguida, seus tipos consumindo ficaria assim:

public class FooService : IFooService {
  public FooService(ILogger<FooService> logger) { ... }
}

Se você é inflexível contra a interface ILogger<T>, você poderia fazer algo mais criativo, como um provedor personalizado, que lê a IContext para determinar o tipo de pai.

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

Então tipos consumindo funcionaria assim:

public class FooService : IFooService {
  public FooService(ILogger logger) { ... }
}

Outras dicas

Se não me engano você, porque não basta ter o seu GericLogger Construtor ter um parâmetro que é o tipo do objeto. Em seguida, faça o seguinte:

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

Eu não li completamente ainda sobre listas de Ninject de parâmetro, mas parece ser uma maneira de injetar um tipo de parâmetro usando um IParameterList.

EDIT:

Parece que este iria funcionar assim:

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

Depois de ter seu ILOG

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

Eu não testei isso, apenas o que parece ser a partir da fonte Ninject (estou olhando para uma árvore recente Ninject2)

EDIT:

Você quer passar ConstructorArgument, não Parameter. Atualizado para refletir.

Este código imprime: "chamados a partir 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:

Outro método que pode ser útil é a utilização de O Bind (). Para (). WithConstructorArgument () de ligao, que pode ser útil quando usado com ou auto-ligação ou com uma condição .WhenInjectedInto ().

Para elaborar a idéia provedor personalizado, aqui está uma maneira simples de fazê-lo ...

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

Então, em classes que são injetadas:

[Inject]
        public ILog logger { get; set; }    
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top