Autofac - How to create a generated factory with parameters

19,601

Solution 1

Update the Engine class to take a Func<ConnectionType, IConnection> instead of the delegate. Autofac supports creating delegate factories on the fly via Func<T>.

public class Engine : IEngine
{
    private Func<ConnectionType, IConnection> _connectionFactory;

    public Engine(Func<ConnectionType, IConnection> connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public string Process(ConnectionType connectionType)
    {
        var connection = _connectionFactory(connectionType);

        return connection.Open().ToString();
    }
}

In your registration use a lambda that grabs up the parameter and returns the correct IConnection instance.

builder.Register<IConnection>((c, p) =>
{
    var type = p.TypedAs<ConnectionType>();
    switch (type)
    {
        case ConnectionType.Ssh:
            return new SshConnection();
        case ConnectionType.Telnet:
            return new TelnetConnection();
        default:
            throw new ArgumentException("Invalid connection type");
    }
})
.As<IConnection>();

If the connection itself required a dependency you could call Resolve on the c parameter to resolve it from the current call context. For example, new SshConnection(c.Resolve<IDependency>()).

Solution 2

If you need to select an implementation type based on paramter you need to use the IIndex<T,B> implicit relation type:

public class Engine : IEngine
{
    private IIndex<ConnectionType, IConnection> _connectionFactory;

    public Engine(IIndex<ConnectionType, IConnection> connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public string Process(ConnectionType connectionType)
    {
        var connection = _connectionFactory[connectionType];

        return connection.Open().ToString();
    }
}

And register your IConnection implementations with the enum keys:

builder.RegisterType<Engine>()
.           As<IEngine>()
    .InstancePerDependency();

builder.RegisterType<SshConnection>()
    .Keyed<IConnection>(ConnectionType.Ssh);
builder.RegisterType<TelnetConnection>()
    .Keyed<IConnection>(ConnectionType.Telnet);

If you want to keep your ConnectionFactory you can manually register it to use an IIndex<T,B> internally with:

builder.Register<ConnectionFactory>(c =>
{
    var context = c.Resolve<IComponentContext>();
    return t => context.Resolve<IIndex<ConnectionType, IConnection>>()[t];
});

In this case you still need to register your IConnection types as keyed but your Engine implementation can remain the same.

Share:
19,601

Related videos on Youtube

Elie
Author by

Elie

Updated on September 15, 2022

Comments

  • Elie
    Elie over 1 year

    I am trying to create with Autofac a 'generated' factory that will resolve dependencies in real-time based on an enum parameter.

    Given the following interfaces/classes:

    public delegate IConnection ConnectionFactory(ConnectionType connectionType);
    
    public enum ConnectionType
    {
        Telnet,
        Ssh
    }
    
    public interface IConnection
    {
        bool Open();
    }
    
    public class SshConnection : ConnectionBase, IConnection
    {
        public bool Open()
        {
            return false;
        }
    }
    
    public class TelnetConnection : ConnectionBase, IConnection
    {
        public bool Open()
        {
            return true;
        }
    }
    
    public interface IEngine
    {
        string Process(ConnectionType connectionType);
    }
    
    public class Engine : IEngine
    {
        private ConnectionFactory _connectionFactory;
    
        public Engine(ConnectionFactory connectionFactory)
        {
            _connectionFactory = connectionFactory;
        }
    
        public string Process(ConnectionType connectionType)
        {
            var connection = _connectionFactory(connectionType);
    
            return connection.Open().ToString();
        }
    }
    

    I'd like to use Autofac to generate some sort of factory that has a method that receives one parameter: ConnectionType and returns the correct connection object.

    I started with the following registrations:

    builder.RegisterType<AutoFacConcepts.Engine.Engine>()
        .As<IEngine>()
        .InstancePerDependency();
    
    builder.RegisterType<SshConnection>()
        .As<IConnection>();
    builder.RegisterType<TelnetConnection>()
        .As<IConnection>();
    

    I then continued to play with the TelnetConnection/SshConnection registrations with different options:

    1. Named
    2. Keyed
    3. Metadata

    I couldn't find the correct combination of the registrations that will allow me to define a generated factory delegate that will return the correct connection object (SshConnection for ConnectionType.Ssh and TelnetConnection for ConnectionType.Telnet).

  • nemesv
    nemesv over 8 years
    The point in using the IIndex is to avoid manually writing this big switch cases so this logic can be handled by Autofac. In the long run if this types have multiple constructor arguments this whole code could get messy quite quickly.
  • nemesv
    nemesv over 8 years
    If the OP wants to keep the the ConnectionFactory delegate instead of the Func<ConnectionType, IConnection> then the following registration should work: builder.Register<IConnection>((c, p) => { var type = p.Named<ConnectionType>("connectionType"); switch (type) { case ConnectionType.Ssh: .... } }) .As<IConnection>(); the point is the the named parameter name "connectionType" has the match the parameter in the delegate declaration.
  • Alex Meyer-Gleaves
    Alex Meyer-Gleaves over 8 years
    I certainly don't mind IIndex but then again I'm always pretty keen on keeping Autofac as my container! :D
  • Elie
    Elie over 8 years
    Thanks Alex! your solution definately answers my original question. Looking at your code, I wonder if I could use this as foundation to create some sort of object builder (or a service locator) - as bad as this sounds, I do have some other requirements that demand that I create objects based on type information that will be provided from a database. It's a bit too long to cover in a comment so maybe I'll ask another question but I'd love to hear your thoughts on creating such a thing with Autofac
  • Alex Meyer-Gleaves
    Alex Meyer-Gleaves over 8 years
    I’m not sure what your requirements are but if you could avoid service location that would definitely be a good thing. If you know what the types are at the time of building the container you can use assembly scanning to register the types. docs.autofac.org/en/latest/register/scanning.html How useful that will be depends on when the objects need to be created and what is responsible for creating them. I'll keep an eye out for another question that I could respond to with some more specific feedback.
  • Derek Greer
    Derek Greer almost 7 years
    It would be great if the Autofac docs showed more detailed examples. It's not readily apparent how the example here relates to Autofac's delegate factory documentation given the examples therein don't show explicit factory registration. That said, how would you describe this usage pattern as opposed to what is shown in the Autofac docs? Explicit factory delegate registration verses the implicit?
  • Stefan Glienke
    Stefan Glienke over 6 years
    What's the point of using a DI container if you then have new statements in that delegate - what if those connections need other dependencies?