I'm a beginner struggling with IoC and DI. I'd like to be able to resolve the connection and connection factory dynamically using autofac (or any other suitable .NET IoC tool).
A scenario could be changing the connection implementation to another one with more facilities for tracing etc.
When I apply DI and IoC to the code below, I get a mess of namedParameter in constructors etc. The connection factory returns a new connection with a unique port (silly example, just to show I need to keep some sort of state in the factory)
I figure I could use property injection for the IP and port range, but that way, I wouldn't be guaranteed that the connections would have an IP or port, which is the point of a constructor. Also, the named parameters make me dependent on the names of the arguments as well.
Ideas, patterns, IoC pointers are much appreciated!
Update:
More specific: How could I change the connection class to be injectable? Should I go with property injection? Or any tricks I could do get a more type-safe resolving with constructor arguments?
public interface IConnection {
void Open();
void Close();
string Execute(string command);
}
public interface IConnectionFactory {
IConnection CreateConnection();
}
public class Connection : IConnection {
...
public Connection(String ip, int port) {
_ip = ip;
_port = port;
}
public string Execute() {}
public void Open() {}
public void Close() {}
}
public class ConnectionFactory : IConnectionFactory {
//How would I resolve this?
public ConnectionFactory(string ip, int fromPort) {
...
}
public IConnection CreateConnection() {
//How would I resolve this?
return new Connection(ip, fromPort++);
}
}
Now, the usage:
//Register
builder.RegisterType<Connection>().As<IConnection>();
builder.RegisterType<ConnectionFactory>().As<IConnectionFactory>().SingleInstance();
...
var connection = container.Resolve<IConnectionFactory>(
new NamedParameter("ip", "127.0.0.1"),
new NamedParameter("fromPort", 80).CreateConnection());
An alternative to passing the constructor arguments at resolve-time is to encode those arguments in the registration function:
builder
.Register(c => new ConnectionFactory("127.0.0.1", 80))
.As<IConnectionFactory>()
.SingleInstance();
Autofac will use that function whenever it needs to create the connection factory instance.
Since we configured ConnectionFactory
as SingleInstance
, it will be shared amongst all components which depend on IConnectionFactory
. This means ConnectionFactory
needs to keep its own state between calls to CreateConnection
:
public class ConnectionFactory : IConnectionFactory
{
private int _fromPort;
public ConnectionFactory(string ip, int fromPort)
{
...
_fromPort = fromPort;
}
public IConnection CreateConnection()
{
return new Connection(ip, _fromPort++);
}
}
If you have a one-off ConnectionFactory
which, say, uses a different IP, you can use a named registration:
builder
.Register(c => new ConnectionFactory("192.168.0.1", 80))
.Named<IConnectionFactory>("AlernateConnectionFactory")
.SingleInstance();
When you want a component to use that particular factory instead of the default one, you can use the ResolveNamed
method:
builder.Register(c => new Foo(c.ResolvedNamed<IConnectionFactory>("AlernateConnectionFactory")));
This is a handy technique to configure a type in multiple ways and use them in specific places.
I am not experienced with Autofac but I solved a very similar problem in Unity. Here's a snippet:
When I configure my container I do something like this (this can even be done in the config file):
string connectionString = ConfigurationManager.ConnectionStrings["XYZ"].ConnectionString;
Container.RegisterType<IConnection, Connection>(new PerThreadLifetimeManager(),
new InjectionConstructor(connectionString));
Later on, I can build the connections by just saying:
IConnection myConnection = Container.Resolve<IConnection>();
or specifying a dependency property:
[Dependency]
IConnection Connection {get;set;}
or as an injectable parameter for constructor injection:
[InjectionConstructor]
public SomeClassThatUsesConnections(IConnection connection)
{ ... }
I left the PerThreadLifetimeManager to point out that I let the IoC deal with ensuring that no two threads are sharing a connection.
Although the difference with your sample in the question is subtle, it does remove the parameters from each instantiation which allows you to have it as a dependency property or as part of an injection constructor.
Hope that helps.
A flexible approach might be to create an "AppConfigConnectionFactory" (or WebConfig, DBConfig, etc...) and externalize these properties via configuration. I've never felt right loading configuration data straight from the DI framework.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With