Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the type being resolved to resolve a dependency

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.

like image 245
Thomas Levesque Avatar asked Feb 09 '16 16:02

Thomas Levesque


People also ask

What is resolved dependency?

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.

How to resolve dependency in c#?

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.

What is dependency injection C# with example?

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.


1 Answers

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));
    }
}
like image 99
Randy supports Monica Avatar answered Oct 07 '22 01:10

Randy supports Monica