grokky
grokky

Reputation: 9255

Autofac parameterized instantiation that resolves differently for different parameters

I'm using Autofac with ASP.NET Core.

My dependency is a Reporter:

public class Reporter {
  public Reporter (bool doLogging) { DoLogging = doLogging ; }
  public string DoLogging { get; set; }
  // other stuff
}

I need to use it like this:

public class Foo
{
  public Foo(Func<bool, Reporter> reporterFactory) { _reporterFactory = reporterFactory; }
  private readonly Func<bool, Reporter> _reporterFactory;
}

And I want it to resolve like this:

_reporterFactory(false) ---> equivalent to ---> new Reporter(false)
_reporterFactory(true)  ---> equivalent to ---> new Reporter(true)

I want the same instance per request (i.e. Autofac's InstancePerLifetimeScope), for the same bool parameter. When I call _reporterFactory(false) multiple times, I want the same instance. And when I call _reporterFactory(true) multiple times, I want the same instance. But those two instances must be different to each other.

So I register it like this:

builder
  .Register<Reporter>((c, p) => p.TypedAs<bool>() ? new Reporter(true): new Person(false))
  .As<Reporter>()
  .InstancePerLifetimeScope();    // gives "per HTTP request", which is what I need

However, when I resolve I get the same instances regardless of the bool argument:

var reporter            = _reporterFactory(false);
var reporterWithLogging = _reporterFactory(true);
Assert.That(reporter, Is.Not.SameAs(reporterWithLogging));     // FAIL!

The documentation for "Parameterized Instantiation" says

resolve the object more than once, you will get the same object instance every time regardless of the different parameters you pass in. Just passing different parameters will not break the respect for the lifetime scope.

Which explains the behavior. So how do I register it correctly?

Upvotes: 2

Views: 993

Answers (1)

tdragon
tdragon

Reputation: 3329

As mentioned in comments, you could use keyed services to achieve your goal:

builder.Register(c => new Reporter(true)).Keyed<IReporter>(true).InstancePerLifetimeScope();
builder.Register(c => new Reporter(false)).Keyed<IReporter>(false).InstancePerLifetimeScope();

The thing is, if you want to inject it to another class, you would have to inject it with IIndex<bool, IReporter>:

public class Foo
{
    public Foo(IIndex<bool, IReporter> reporters)
    {
        var withLogging = reporters[true];
        var withoutLogging = reporters[false];
    }
}

IIndex is Autofac's interface, which makes your component tight coupled with the container, and this may not be desirable. To avoid this, you could additionally register the factory, like this:

builder.Register<Func<bool, IReporter>>((c,p) => withLogging => c.ResolveKeyed<IReporter>(withLogging)).InstancePerLifetimeScope();

public class Foo
{
    public Foo(Func<bool, IReporter> reporters)
    {
        var withLogging = reporters(true);
        var withoutLogging = reporters(false);
    }
}

Now you have the working solution without coupling to the container itself.

Upvotes: 3

Related Questions