Question

I have a simpler "ServiceHelper" class that takes two parameters in the constructor:

public ServiceHelper(ILogger<ServiceHelper> log, string serviceName)

(ILogger generic wrapper for NLog that Autofac is providing just fine, and the serviceName is the name of a Windows service to control that I need to provide at runtime.)

I'm having trouble wrapping my head around how to create new instances of this class at runtime passing in different service names, using Autofac. Something like this doesn't work of course since I need to specify different service names at runtime:

builder.RegisterType<ServiceHelper>().As<IServiceHelper>().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency();

From what I've read, its a bad habit to pass the container around and call Resolve manually right (the Service Locator "anti-pattern" the AutoFac warns about), or is it? If I did that then I could do

container.Resolve<ServiceHelper>(new NamedParameter("serviceName", "some service name"));

But to even get that far I'm not quite sure how to get Autofac to inject the container into the classes, it would just need to register itself how exactly, like this? And then have my classes require an IContainer in their constructors? (This is in a C# Service using constructor injection)

builder.RegisterType<Container>().As<IContainer>().InstancePerDependency();

I read about delegate factories too but that doesn't seem to get away from having to pass the container around.

Really most of my classes that consume the ServiceHelper, just need 1 or 2 ServiceHelpers for specific service names, so its not like I'm making thousands with unexpected serviceName parameters, this is just making my head hurt a little.

Was it helpful?

Solution

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>();
    }
}

OTHER TIPS

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

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...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top