Reputation: 93
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
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