0

Let's assume the following classes

class Foo : IFoo {
    Foo(IBar bar) {}
}

class Bar : IBar {
    Bar(IBaz baz)       
}

My container is set up so you can differentiate on IBaz by key.

builder.RegisterType<Baz1>().Keyed<IBaz>("1");    
builder.RegisterType<Baz2>().Keyed<IBaz>("2");

Now I would like to create two classes who have an IFoo injected, but further down they need to be injected with either Baz1 or Baz2.

class MyClassA {
    MyClassA(IFoo foo) {
        var baz = foo.GetBar().GetBaz();
        //baz should be of type Baz1 
    }
}

class MyClassB {
    MyClassB(IFoo foo) {
       var baz = foo.GetBar().GetBaz();
       //baz should be of type Baz2
    }  
}

How do I configure/setup something like that? Preferable with an attribute in MyClassA and MyClassB.

Stijn Van Antwerpen
  • 1,840
  • 17
  • 42
  • Have you read the docs [on KeyedService](https://autofac.readthedocs.io/en/latest/advanced/keyed-services.html) and things like the [KeyFilterAttribute](https://autofac.readthedocs.io/en/latest/advanced/keyed-services.html#resolving-with-attributes)? – Travis Illig May 07 '19 at 14:48
  • Yes I have. KeyFilterAttribute would be fine if the decision would have been made in the Bar. just apply the keyfilter to it and your done. But the Bar just needs to get a IBaz injected (either Baz1 or Baz2). It is up to MyClassA to know that eventually Baz1 will be required. However, MyClass1 does not get a IBaz injected, so no keyfilter can be applied there. I could apply a keyfilter on IFoo and chain it all the way down, that is the part I don't know how to do. – Stijn Van Antwerpen May 08 '19 at 05:55
  • OK, cool, just making sure. I couldn't tell from your question if that's something you'd thought of. – Travis Illig May 08 '19 at 14:00

2 Answers2

3

Your question sort of hovers between two of our Autofac FAQs:

It may not be the answer you want, but... in both of these cases, the answer is that there's an issue with the design that should be addressed rather than trying to force this to happen.

We explain why this is a design problem on the 'pass a parameter' FAQ. It says "parameter" but you could read the same thing as "resolving a specific implementation of an interface." I'll update/tweak the text so it applies here:

Technically speaking, you’re resolving an IFoo - a component that doesn’t need to know about the IBaz implementation. The implementation of IFoo could change, or even the implementation of IBar. You could register a stub for testing, or switch up how things work so that implementation tie isn't required.

Forcing the implementation tie of IBaz to the specific IFoo being required breaks the decoupling that interface-based development and inversion of control gives you by assuming that you "know" how the entire dependency chain is being resolved.

This is also basically the note on the 'implementation by context' FAQ. In that answer, there's a whole analogy using the object-oriented "animals" hierarchy to illustrate in a concrete fashion why it's not good. I'll not re-paste that here. However, I'll reiterate that treating these two IBaz implementations differently breaks the Liskov substitution principle - that you should be able to swap the IBaz implementations without breaking things. "Knowing" that one is substantially different than the other naturally implies that they're not the same and, thus, shouldn't implement the same interface. (Maybe a common base interface, but when they're consumed, the interface being consumed wouldn't be the same if the underlying implementation can't be treated the same.)

I recommend redesigning the interfaces so you don't have this problem. If that's not possible... well, honestly, there's not a much better solution for it than the answer you already posted. It's not easy to accomplish because it's not generally something you should try accomplishing.

Again, sorry that's probably not the answer you want, but I think that's the answer.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
  • You are right, I don't like the answer, but then again, I think you are right. – Stijn Van Antwerpen May 14 '19 at 11:33
  • I read the docs that you linked to. That approach works well when we know the parameter value at registration time—but what if we don't know it until resolution time? How can we handle that scenario without ending up with a convoluted mess like [this](https://stackoverflow.com/q/76106973)? – InteXX Apr 26 '23 at 08:37
  • OK, I managed to [tame it](https://stackoverflow.com/a/76109665) significantly. Could you comment on whether my proposed solution aligns well with good design practices? Have I broken any rules here? – InteXX Apr 26 '23 at 16:51
0

Well, this would do the trick.

        builder.RegisterType<MyClass1>()
            .WithParameter(
                (pi, ctx) => pi.Name == "foo",
                (pfoo, cfoo) => cfoo.Resolve<IFoo>(new ResolvedParameter(
                    (pbar, cbar) => pbar.Name == "bar",
                    (pbar, cbar) => cbar.Resolve<IBar>(new ResolvedParameter(
                        (pbaz, cbaz) => pbaz.Name == "baz",
                        (pbaz, cbaz) => cbaz.ResolveKeyed<IBaz>("1"))))))
            .AsSelf();

        builder.RegisterType<MyClass2>()
            .WithParameter(
                (pi, ctx) => pi.Name == "foo",
                (pfoo, cfoo) => cfoo.Resolve<IFoo>(new ResolvedParameter(
                    (pbar, cbar) => pbar.Name == "bar",
                    (pbar, cbar) => cbar.Resolve<IBar>(new ResolvedParameter(
                        (pbaz, cbaz) => pbaz.Name == "baz",
                        (pbaz, cbaz) => cbaz.ResolveKeyed<IBaz>("2"))))))
            .AsSelf();

However, I am not convinced that this is the preferred way for doing stuff like that.

Stijn Van Antwerpen
  • 1,840
  • 17
  • 42