Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac - resolving runtime parameters without having to pass container around

Tags:

c#

autofac

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.

like image 792
Jeremy Avatar asked Mar 07 '14 20:03

Jeremy


People also ask

What is Autofac container?

Autofac is an addictive IoC container for . NET. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. This is achieved by treating regular . NET classes as components.

Why should I use Autofac?

AutoFac provides better integration for the ASP.NET MVC framework and is developed using Google code. AutoFac manages the dependencies of classes so that the application may be easy to change when it is scaled up in size and complexity.

How do I register an Autofac service?

Register by Type var builder = new ContainerBuilder(); builder. RegisterType<ConsoleLogger>(); builder. RegisterType(typeof(ConfigReader)); When using reflection-based components, Autofac automatically uses the constructor for your class with the most parameters that are able to be obtained from the container.


2 Answers

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>();     } } 
like image 145
Christian Specht Avatar answered Sep 20 '22 18:09

Christian Specht


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

like image 30
raterus Avatar answered Sep 23 '22 18:09

raterus