Autofac optional/lazy dependencies
Asked Answered
E

1

5

If I put Lazy in constructor of my object, and X is not registered in container I got dependency resolution exception.

Why am I getting this exception? I dislike it, because I cannot choose component at runtime. Example usecase:

class Controller
{
   public Controller(Lazy<A> a, Lazy<B> b) { /* (...) */ }

 Lazy<A> a;
 Lazy<B> b;

 public IActionResult Get(){
  if(someConfig)
    return Json(a.Value.Execute());
  else
    return Json(b.Value.Execute());
 }
}

To do so I need to register both components A an B. My program fails even if B is never used. I would like to have B be optional, and still managed by autofac.

This is even bigger issue if I have list of components, and want only one to be used. For example:

class Controller
{
    Controller(IEnumerable<Component> components) { /* (...) */ }

    IActionResult Get()
    {
        return components.First(n => n.Name == configuredComponent).Execute();

    }

}

I am no longer getting exception is something is not registered, however still everything is constructed. Also it would be awkward to use.

Ensor answered 25/1, 2017 at 16:21 Comment(0)
D
15

If you add a reference to a Lazy<T> component, Autofac has to know (basically) how to create the internal function that will run should you want to resolve it, even if you don't resolve it.

Basically, it needs to be able to create this in memory:

var lazy = new Lazy<T>(() => scope.Resolve<T>());

Autofac requires all of the things you want to resolve to be registered. It doesn't let you register things on the fly - it must be explicit. So the thing you're trying to do won't work (as you saw).

Instead, use a single interface and two different implementations of that interface. Change the registration based on your configuration value.

var builder = new ContainerBuilder();
if(someConfig)
{
  builder.RegisterType<A>().As<IService>();
}
else
{
  builder.RegisterType<B>().As<IService>();
}

Then in your controller, inject the interface rather than the concrete class.

public MyController(Lazy<IService> service)

There are also other options you could do, like use metadata for components or keyed services. For example, you could add some metadata based on your configuration and resolve using that.

builder.RegisterType<A>()
       .As<IService>()
       .WithMetadata("Name", "a");
builder.RegisterType<B>()
       .As<IService>()
       .WithMetadata("Name", "b");

In the controller, you'd get a dictionary of them:

public MyController(IEnumerable<Meta<IService>> services)
{
  var service = services.First(s => s.Metadata["Name"].Equals(someConfig);
}

That's a very short example, but the docs show a lot more.

In any case, the interface is really going to be your key to success. If you're just using concrete classes, they'll have to be registered whether you use them or not.

Damar answered 25/1, 2017 at 17:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.