I have several classes that take a dependency of type ILogger
. The implementation of ILogger
needs to know the type for which it is the logger, i.e. the ILogger
for Foo
will be new Logger(typeof(Foo))
, for Bar
it will be new Logger(typeof(Bar))
, etc.
I would like the proper logger to be injected automatically by Unity; in other words, when I call container.Resolve<Foo>()
, I want a new Logger(typeof(Foo))
to be injected into the Foo
instance.
How can I set this up in Unity? Is there a way to pass the type being resolved to the dependency?
(In my real code, I actually have a ILoggerFactory
with a Create
method, which also takes a type as a parameter. So I could just pass the factory to my classes, and they would call Create
themselves to get the appropriate logger, but it's not as elegant as what I'd like to achieve)
Some code to make things clearer:
interface ILogger
{
...
}
class Logger : ILogger
{
private readonly Type _type;
public Logger(Type type)
{
_type = type;
}
...
}
class Foo
{
private readonly ILogger _logger;
public Foo(ILogger logger) // here I want a Logger with its type set to Foo
{
_logger = logger;
}
}
This related question shows exactly what I'm trying to do, and the accepted answer is exactly the kind of thing I'm looking for... but it's for NInject, not Unity.
Dependency resolution is a process that consists of two phases, which are repeated until the dependency graph is complete: When a new dependency is added to the graph, perform conflict resolution to determine which version should be added to the graph.
Resolve dependencies using IServiceProvider You can use the IServiceCollection interface to create a dependency injection container. Once the container has been created, the IServiceCollection instance is composed into an IServiceProvider instance. You can use this instance to resolve services.
Using dependency injection, we can pass an instance of class C to class B, and pass an instance of B to class A, instead of having these classes to construct the instances of B and C. In the example, below, class Runner has a dependency on the class Logger.
Here's a container extension that will set the Type parameter of the Logger constructor to the Type that the ILogger is being injected into.
The transient IBuilderContext.Policies is used to store the type that the ILogger is being injected into.
Maybe it's more complicated than it needs to be but this seems to work
public class LoggerExtension : UnityContainerExtension
{
public static NamedTypeBuildKey LoggerBuildKey = new NamedTypeBuildKey<Logger>();
protected override void Initialize()
{
Context.Strategies.Add(new LoggerTrackingPolicy(), UnityBuildStage.TypeMapping);
Context.Strategies.Add(new LoggerBuildUpStrategy(), UnityBuildStage.PreCreation);
}
}
public class LoggerTrackingPolicy : BuilderStrategy
{
public LoggerTrackingPolicy()
{
}
public override void PreBuildUp(IBuilderContext context)
{
if (context.BuildKey.Type != typeof(Logger))
{
var loggerPolicy = context.Policies.Get<ILoggerPolicy>(LoggerExtension.LoggerBuildKey);
if (loggerPolicy == null)
{
loggerPolicy = new LoggerPolicy();
context.Policies.Set<ILoggerPolicy>(loggerPolicy, LoggerExtension.LoggerBuildKey);
}
loggerPolicy.Push(context.BuildKey.Type);
}
}
}
public class LoggerBuildUpStrategy : BuilderStrategy
{
public LoggerBuildUpStrategy()
{
}
public override void PreBuildUp(IBuilderContext context)
{
if (context.BuildKey.Type == typeof(Logger))
{
var policy = context.Policies.Get<ILoggerPolicy>(LoggerExtension.LoggerBuildKey);
Type type = policy.Peek();
if (type != null)
{
context.AddResolverOverrides(new ParameterOverride("type", new InjectionParameter(typeof(Type), type)));
}
}
}
public override void PostBuildUp(IBuilderContext context)
{
if (context.BuildKey.Type != typeof(Logger))
{
var policy = context.Policies.Get<ILoggerPolicy>(LoggerExtension.LoggerBuildKey);
policy.Pop();
}
}
}
public interface ILoggerPolicy : IBuilderPolicy
{
void Push(Type type);
Type Pop();
Type Peek();
}
public class LoggerPolicy : ILoggerPolicy
{
private Stack<Type> types = new Stack<Type>();
public void Push(Type type)
{
types.Push(type);
}
public Type Peek()
{
if (types.Count > 0)
{
return types.Peek();
}
return null;
}
public Type Pop()
{
if (types.Count > 0)
{
return types.Pop();
}
return null;
}
}
How it works is: when a type that is not a Logger is trying to be resolved then, during the TypeMapping stage (before any creation), the type is pushed on a stack. Later, before creation, if the type is a Logger then the type it is being injected into is peeked from the stack and that type used as a resolver override. After creation, if the type is not a Logger, it is popped from the stack.
And some code to make sure it's working (I added a Type property to the logger just to verify it's set properly):
class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<ILogger, Logger>();
container.AddNewExtension<LoggerExtension>();
var a = container.Resolve<A>();
var b = container.Resolve<B>();
var c = container.Resolve<C>();
var d = container.Resolve<D>();
var x = container.Resolve<X>();
}
}
public interface ILogger
{
Type Type { get; }
}
public class Logger : ILogger
{
private readonly Type _type;
public Logger(Type type)
{
_type = type;
}
public Type Type { get { return _type; } }
}
public class A
{
public A(ILogger logger)
{
System.Diagnostics.Debug.Assert(logger.Type == typeof(A));
}
}
public class B
{
public B(ILogger logger)
{
System.Diagnostics.Debug.Assert(logger.Type == typeof(B));
}
}
public class C
{
public C(A a, D d, B b, ILogger logger)
{
System.Diagnostics.Debug.Assert(logger.Type == typeof(C));
}
}
public class D
{
public D()
{
}
}
public class X
{
public X(Y y)
{
}
}
public class Y
{
public Y(ILogger logger)
{
System.Diagnostics.Debug.Assert(logger.Type == typeof(Y));
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With