Search code examples
c#dependency-injectionninject

How to inject instance depending on the final injection chain target?


I have classes: X1 <- Y <- Z <- Config (arrow means injection via constructor) and X2 <- Y <- Z <- Config. Z expects some configuration (Config class), but the instance depends on the final type: X1 and X2 (type itself or a key they have defned somehow). In this example there should be two different instances of each Y, Z and Config class.

How can I use different Config in Z depending on where it finally is used (X1 or X2)?

    class X1
    {
        public X1(Y y)
        {
            int c = y.Z.Config.C; // This config variable is connected with X1.
        }
    }

    class X2
    {
        public X2(Y y)
        {
            int c = y.Z.Config.C; // This config variable is different than the one for X1.
        }
    }

    class Y
    {
        public Z Z { get; }

        public Y(Z z)
        {
            Z = z;
        }
    }


    class Z
    {
        public Config Config { get; }

        public Z(Config config)
        {
            Config = config;
        }
    }

    class Config
    {
        public int C { get; set; }
    }

I could do sth like below, but it seems very fishy and smelly (a rough example):

    Bind<Config>().ToMethod(x =>
    {
        // Return proper config object depending on the classes found in the injection chain...
        IRequest req = x.Request;
        while (req != null)
        {
            if (req.Service.UnderlyingSystemType == typeof(X1))
            {
                return configForX1;
            }

            if (req.Service.UnderlyingSystemType == typeof(X2))
            {
                return configForX2;
            }

            req = req.ParentRequest;
        }

        throw new Exception("Oh no.");
    });

Or to make it less fishy should I do:

    class X1
    {
        public X1([Named("X1")] Config config, Y y)
        {
            y.SetConfig(config);
        }
    }

    class Y
    {
        private readonly Z _z;

        public Y(Z z)
        {
            _z = z;
        }

        public void SetConfig(Config config)
        {
            _z.SetConfig(config);
        }
    }

    class Z
    {
        private Config _config;

        public void SetConfig(Config config)
        {
            _config = config;
        }
    }

and

    Bind<MetricsApiConfiguration>().To().Named("X1");
    Bind<MetricsApiConfiguration>().To().Named("X2");

Any other (better) ideas?


Solution

  • WhenInjectedInto is close, but as you noted, only looks at the current request. I created an extension method to address this exact use case:

        public static IBindingInNamedWithOrOnSyntax<T> WhenAnyAncestorIs<T>(this IBindingWhenSyntax<T> binding, params Type[] types)
        {
            bool Matches(IRequest request)
            {
                Type target = request.Target?.Member?.ReflectedType;
                return (target != null && types.Any(t => t == target))
                    || (request.ParentRequest != null && Matches(request.ParentRequest));
            }
    
            return binding.When(Matches);
        }
    

    and use like:

    Bind<IConfigSource>().To<DataConfigSource>()
        .WhenAnyAncestorIs(
            typeof(DataConfigurationRepository),
            typeof(ManifestRepository),
            typeof(DataManager)
        )
        .InSingletonScope();
    

    you will need an additional binding for requests that do not use those Types.