JoefGoldstein
JoefGoldstein

Reputation: 93

Autofac: Resolving dependencies with parameters

I'm currently learning the API for Autofac, and I'm trying to get my head around what seems to me like a very common use case.

I have a class (for this simple example 'MasterOfPuppets') that has a dependency it receives via constructor injection ('NamedPuppet'), this dependency needs a value to be built with (string name):

    public class MasterOfPuppets : IMasterOfPuppets
    {
        IPuppet _puppet;

        public MasterOfPuppets(IPuppet puppet)
        {
            _puppet = puppet;
        }
    }

    public class NamedPuppet : IPuppet
    {
        string _name;

        public NamedPuppet(string name)
        {
            _name = name;
        }
    }

I register both classes with their interfaces, and than I want to resolve IMasterOfPuppets, with a string that will be injected into the instance of 'NamedPuppet'.

I attempted to do it in the following way:

IMasterOfPuppets master = bs.container.Resolve<IMasterOfPuppets>(new NamedParameter("name", "boby"));

This ends with a runtime error, so I guess Autofac only attempts to inject it to the 'MasterOfPuppets'.

So my question is, how can I resolve 'IMasterOfPuppets' only and pass parameter arguments to it's dependency, in the most elegant fashion? Do other ioc containers have better solutions for it?

Upvotes: 2

Views: 1685

Answers (1)

Travis Illig
Travis Illig

Reputation: 23894

Autofac doesn't support passing parameters to a parent/consumer object and having those parameters trickle down into child objects.

Generally I'd say requiring the consumer to know about what's behind the interfaces of its dependencies is bad design. Let me explain:

From your design, you have two interfaces: IMasterOfPuppets and IPuppet. In the example, you only have one type of IPuppet - NamedPuppet. Keeping in mind that the point of even having the interface is to separate the interface from the implementation, you might also have this in your system:

public class ConfigurablePuppet : IPuppet
{
  private string _name;
  public ConfigurablePuppet(string name)
  {
    this._name = ConfigurationManager.AppSettings[name];
  }
}

Two things to note there.

First, you have a different implementation of IPuppet that should work in place of any other IPuppet when used with the IMasterOfPuppets consumer. The IMasterOfPuppets implementation should never know that the implementation of IPuppet changed... and the thing consuming IMasterOfPuppets should be even further removed.

Second, both the example NamedPuppet and the new ConfigurablePuppet take a string parameter with the same name, but it means something different to the backing implementation. So if your consuming code is doing what you show in the example - passing in a parameter that's intended to be the name of the thing - then you probably have an interface design problem. See: Liskov substitution principle.

Point being, given that the IMasterOfPuppets implementation needs an IPuppet passed in, it shouldn't care how the IPuppet was constructed to begin with or what is actually backing the IPuppet. Once it knows, you're breaking the separation of interface and implementation, which means you may as well do away with the interface and just pass in NamedPuppet objects all the time.

As far as passing parameters, Autofac does have parameter support.

The recommended and most common type of parameter passing is during registration because at that time you can set things up at the container level and you're not using service location (which is generally considered an anti-pattern).

If you need to pass parameters during resolution Autofac also supports that. However, when passing during resolution, it's more service-locator-ish and not so great becausee, again, it implies the consumer knows about what it's consuming.

You can do some fancy things with lambda expression registrations if you want to wire up the parameter to come from a known source, like configuration.

builder.Register(c => {
  var name = ConfigurationManager.AppSettings["name"];
  return new NamedPuppet(name);
}).As<IPuppet>();

You can also do some fancy things using the Func<T> implicit relationship in the consumer:

public class MasterOfPuppets : IMasterOfPuppets
{
    IPuppet _puppet;

    public MasterOfPuppets(Func<string, IPuppet> puppetFactory)
    {
        _puppet = puppetFactory("name");
    }
}

Doing that is the equivalent of using a TypedParameter of type string during the resolution. But, as you can see, that comes from the direct consumer of IPuppet and not something that trickles down through the stack of all resolutions.

Finally, you can also use Autofac modules to do some interesting cross-cutting things the way you see in the log4net integration module example. Using a technique like this allows you to insert a specific parameter globally through all resolutions, but it doesn't necessarily provide for the ability to pass the parameter at runtime - you'd have to put the source of the parameter inside the module.

Point being Autofac supports parameters but not what you're trying to do. I would strongly recommend redesigning the way you're doing things so you don't actually have the need to do what you're doing, or so that you can address it in one of the above noted ways.

Hopefully that should get you going in the right direction.

Upvotes: 7

Related Questions