Autofac - How to create a generated factory with parameters
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.
Related videos on Youtube
Elie
Updated on September 15, 2022Comments
-
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:
- Named
- Keyed
- 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 over 8 yearsThe 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 over 8 yearsIf the OP wants to keep the the
ConnectionFactory
delegate instead of theFunc<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 over 8 yearsI certainly don't mind
IIndex
but then again I'm always pretty keen on keeping Autofac as my container! :D -
Elie over 8 yearsThanks 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 over 8 yearsI’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 almost 7 yearsIt 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 over 6 yearsWhat's the point of using a DI container if you then have new statements in that delegate - what if those connections need other dependencies?