Autofac - resolving runtime parameters without having to pass container around
Yes, passing the container around everywhere is an anti-pattern.
You can avoid it by using a factory like this:
(note: all code in this answer is untested, I'm writing this in a text editor on a machine without Visual Studio)
public interface IServiceHelperFactory
{
IServiceHelper CreateServiceHelper(string serviceName);
}
public class ServiceHelperFactory : IServiceHelperFactory
{
private IContainer container;
public ServiceHelperFactory(IContainer container)
{
this.container = container;
}
public IServiceHelper CreateServiceHelper(string serviceName)
{
return container.Resolve<ServiceHelper>(new NamedParameter("serviceName", serviceName));
}
}
On startup, you register the ServiceHelperFactory
in Autofac, like everything else:
builder.RegisterType<ServiceHelperFactory>().As<IServiceHelperFactory>();
Then, when you need a ServiceHelper
somewhere else, you can get the factory via constructor injection:
public class SomeClass : ISomeClass
{
private IServiceHelperFactory factory;
public SomeClass(IServiceHelperFactory factory)
{
this.factory = factory;
}
public void ThisMethodCreatesTheServiceHelper()
{
var helper = this.factory.CreateServiceHelper("some service name");
}
}
By creating the factory itself via constructor injection with Autofac, you make sure that the factory knows about the container, without having to pass the container around by yourself.
I admit, at first glance this solution doesn't look very different than passing the container around directly. But the advantage is that your app is still decoupled from the container - the only place where the container is known (except startup) is inside the factory.
EDIT:
OK, I forgot. As I said above, I'm writing this on a machine without Visual Studio, so I'm not able to test my example code.
Now that I read your comment, I remember that I had a similar problem when I used Autofac and tried to register the container itself.
My problem was that I needed to register the container in the builder.
But to get the container instance to register, I needed to call builder.Build()
...which creates the container, which means that I can't register stuff in the builder afterwards.
I don't remember the error message that I got, but I guess you have the same problem now.
The solution that I found was to create a second builder, register the container there, and then use the second builder to update the one and only container.
Here is my working code from one of my open source projects:
On startup, I register the container::
var builder = new ContainerBuilder();
// register stuff here
var container = builder.Build();
// register the container
var builder2 = new ContainerBuilder();
builder2.RegisterInstance<IContainer>(container);
builder2.Update(container);
...which is then used by a WindowService
to create new WPF windows:
public class WindowService : IWindowService
{
private readonly IContainer container;
public WindowService(IContainer container)
{
this.container = container;
}
public T GetWindow<T>() where T : MetroWindow
{
return (T)this.container.Resolve<T>();
}
}
Resolve should occur for root composition objects only. Calling resolve is near to the same as "newing" up an object, which is a smell test. There are times when the resolution is dynamic and can only be determined on-the-fly, but most dependencies are deterministic and can be registered up front. How to do this with Autofac is the challenge. The awarded answer by @Christian Specht is a good answer, but it assumes everything is determined at runtime.
To define a dependency chain at design time, see SO topic Autofac sub-dependencies chain registration...
I went down the path of the above approach and it works fine, however I found it impossible to unit test due to the fact the "Resolve<>" method in IContainer is a extension method. It also never really felt "right" with all the talk about not passing your container around.
I went back to the drawing board, and found the "correct" way to instantiate objects using Autofac Delegate Factories http://docs.autofac.org/en/latest/advanced/delegate-factories.html