L'utilisation Ninject (ou un autre récipient) Comment puis-je trouver le type qui demande le service?
-
23-08-2019 - |
Question
Supposons que j'ai une interface pour un service:
public interface IFooService
{
void DoSomething();
}
Et une mise en œuvre concrète de ce service qui est générique:
public class FooService<TRequestingClass> : IFooService
{
public virtual void DoSomething() { }
}
Et j'ai une autre classe qui a besoin d'une instance de IFooService:
public class Bar
{
private IFooService _fooService;
public Bar(IFooService fooService)
{
this._fooService = fooService;
}
}
Je dois câbler mon conteneur IoC de sorte que lorsque Bar est créé, il est transmis un argument constructeur de FooService
Un alternatative acceptable pour FooService
public class FooService : IFooService
{
public FooService(string requestingClassName) { }
}
Comment puis-je brancher mon conteneur IoC pour construire une dépendance de cette façon?
Si vous êtes confus pourquoi je voudrais une telle structure wierd, examiner comment fonctionne log4net mieux quand vous obtenez un ILog qui est créé avec log4net.LogManager.GetLogger (typeof (SomeClass)). Je ne veux pas la litière mon code avec des références à Log4net, donc je voudrais écrire une interface simple ILogger, et mettons en œuvre avec quelque chose comme ceci:
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 .... */
}
La solution
La façon la plus simple serait de créer une interface ILogger<T>
:
public class ILogger<T> : ILogger { }
public class GenericLogger<T> : ILogger<T> { ... }
Ensuite, compter sur l'inférence de type générique pour obtenir le type correct. Par exemple, dans Ninject, le tout est que vous auriez besoin de liaison suivante:
Bind(typeof(ILogger<>)).To(typeof(GenericLogger<>));
Alors vos types de consommation se présente comme suit:
public class FooService : IFooService {
public FooService(ILogger<FooService> logger) { ... }
}
Si vous êtes inflexiblement contre l'interface ILogger<T>
, vous pourriez faire quelque chose de plus créatif, comme un fournisseur personnalisé, qui lit le IContext
pour déterminer le type de parent.
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);
}
}
Alors types consommateurs travailleraient comme ceci:
public class FooService : IFooService {
public FooService(ILogger logger) { ... }
}
Autres conseils
Si je ne suis pas vous malentendu, pourquoi ne pas tout simplement votre GericLogger Constructor prendre un paramètre qui est le type de l'objet. Ensuite, faites ceci:
ILog = kernel.Get<ILog>(ParameterList);
Je n'ai pas lu complètement encore sur les listes de paramètres ninject, mais il semble être un moyen d'injecter un type de paramètre à l'aide d'un IParameterList.
EDIT:
On dirait que cela fonctionnerait comme ceci:
ILog = kernel.Get<ILog>(new ConstructorArgument[] {
new ConstructorArgument("ClassName", this.GetType().Name)
})
Ensuite, vous avez votre ILog
class GenericLogger : Ilog
{
GenericLogger(string ClassName) {};
}
Je n'ai pas testé cela, tout ce qui semble être de la source Ninject (je suis à la recherche d'un arbre récent Ninject2)
EDIT:
Vous voulez passer ConstructorArgument, pas de paramètre. Mise à jour pour refléter.
Ce code Prints: "Appelés De 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:
Une autre méthode qui pourrait être utile est d'utiliser le bind (). Pour (). WithConstructorArgument () de liaison, ce qui pourrait être utile lorsqu'il est utilisé soit avec autolimitation ou avec une condition .WhenInjectedInto ().
Pour élaborer l'idée de fournisseur personnalisé, voici un moyen simple de le faire ...
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());
}
}
Ensuite, dans les classes qui sont injectées:
[Inject]
public ILog logger { get; set; }