Mike Mike - 10 months ago 148
C# Question

Autofac Resolve constructor instance from Container?

How can I register a type which takes another type as part of it's constructed without passing an actual instance. Say I have two ISurface types registered. I want to register a Car but i don't want to pass in a brand new instance. I want to use one of the surfaces already defined.

Per the documentation they state :

  • Autofac sees that IDateWriter maps to TodayWriter so starts creating
    a TodayWriter.

  • Autofac sees that the TodayWriter needs an IOutput in its

  • Autofac sees that IOutput maps to ConsoleOutput so creates a new
    ConsoleOutput instance.

Then why must I pass in an instance of Highway when registering a Car? Given that I have two Surfaces's registered how do I specify an existing surface?

var builder = new ContainerBuilder();

builder.RegisterType<Car>().Named<IVehicle>("Car").WithParameter("surface", new Highway());

Why do I need to pass a new Highway()?

Here's my models.

public interface IVehicle
void Move();

public interface ISurface
string SurfaceType { get; }

public class Highway : ISurface
public string SurfaceType => "Pavement";

public class Ocean : ISurface
public string SurfaceType => "Ocean"

public class Car : IVehicle
private ISurface _surface;

public Car(ISurface surface)
_surface = surface;

public void Move()
Console.WriteLine($"I'm traveling at high speeds across {_surface.SurfaceType}");

Answer Source

There's a couple things you can do here:

Option 1

This keeps in line with what you already have. You can still use .WithParameter() but instead pass in a ResolvedParameter instance to explain the parameter to find and how to fulfill the parameter:

builder.RegisterType<Car>().Named<IVehicle>( "Car" )
        new ResolvedParameter(
            ( pInfo, ctx ) => pInfo.Name == "surface",
            ( pInfo, ctx ) => ctx.ResolveNamed<ISurface>( "Highway" )

The first delegate passed to the ResolvedParameter constructor provides a way to find the parameter to fulfill, and the second delegate uses the IComponentContext to resolve the Highway instance from the Autofac container.

Alternatively there is an overload to WithParameter() that you can use without having to explicitly create a ResolvedParameter:

builder.RegisterType<Car>().Named<IVehicle>( "Car" )
    ( pInfo, ctx ) => pInfo.Name == "surface",
    ( pInfo, ctx ) => ctx.ResolveNamed<ISurface>( "Highway" ) );

Option 2

This option uses registration with a lambda expression:

builder.Register( ( ctx ) => new Car( ctx.ResolveNamed<ISurface>( "Highway" ) ) ).Named<IVehicle>( "Car" );

Here you can see that I'm passing a lambda that represents my factory function which will again use the IComponentContext to resolve the Highway from the Autofac container.


I also read from your question that you wanted the same instance of ISurface each time you requested it. If this is what you want, then you'll need to update your ISurface registrations to include .SingleInstance() :

builder.RegisterType<Highway>().Named<ISurface>( "Highway" ).SingleInstance();
builder.RegisterType<Ocean>().Named<ISurface>( "Ocean" ).SingleInstance();

The default lifetime given to registrations is InstancePerDependency which means that the Autofac resolver will give a new instance of the object every time it is requested. SingleInstance is essentially a singleton. You'll only get one instance created and that instance will be returned on every request. Here's a link to that info