I'm using Autofac constructor injection. I need to figure out how to inject a single object instance into more than one constructor argument, without needing to explicitly resolve each argument during the container setup phase.
I have a complex scenario which would be simplified by this behavior; the following example is just a simplified scenario so I can demonstrate the behavior I'm looking for.
Example:
Say I have these two interfaces, IOpenable and ICloseable:
public interface IOpenable
{
void Open();
}
public interface ICloseable
{
void Close();
}
And I have this Door class which implements both of them:
public interface IDoor : IOpenable, ICloseable { }
public class Door : IDoor, IOpenable, ICloseable
{
void Open() { ... }
void Close() { ... }
}
And I have this class which accepts an IOpenable and an ICloseable:
public class DoorHandler : IDoorHandler
{
public DoorHandler(IOpenable openable, ICloseable closeable)
{
...
}
...
}
Question:
Is it possible to tell autofac to inject the same Door object into both arguments whenever both an IOpenable and ICloseable are dependencies in the same constructor?
Note: I can't do:
container.Register<IDoorHandler>( c => {
Door d = c.Resolve<IDoor>();
return new DoorHandler(d,d)
});
That would do what I want, but remember that this DoorHandler class is just an example. In my real code, the "DoorHandler" is really an MVC Controller and I'm registering it with RegisterControllers(). So I can't register it like the above. Besides, sometimes dependency graphs can get overly complex and doing this explicitly in every case can become overwhelming.
I guess what I'm looking for is to be able to do something like:
container.RegisterType<DoorHandler>().As<IDoorHandler>();
container.RegisterType<Door>().As<IDoor>();
container.Register<IOpenable>( c => c.ResolveShared<IDoor>(); );
container.Register<ICloseable>( c => c.ResolveShared<IDoor>(); );
where calls to c.ResolveShared<T>
will all resolve to the same T object if called for more than one argument in same constructor.
Or perhaps:
container.RegisterType<DoorHandler>().As<IDoorHandler>();
container.RegisterType<Door>().As<IDoor>().InstancePerDependencyShared();
container.Register<IOpenable>( c => c.Resolve<IDoor>(); );
container.Register<ICloseable>( c => c.Resolve<IDoor>(); );
Obviously if I was using an InstancePerLifetimeScope or something for the Door object, each resolved Door would be the same instance. But I don't want that, I want a new instance of Door each time a DoorHandler is created, and I want that Door to be passed as both arguments to the DoorHandler constructor.
Ok tricky one :) ... Here's one possible solution for generalised "per constructor" sharing:
builder.RegisterControllers(asm)
.OnPreparing(e => {
var spr = new SharedConstructorParameter(
typeof(IOpenable),
typeof(ICloseable));
e.Parameters = new Parameter[]{ spr }.Concat(e.Parameters);
});
The parameter needs to be set up in the OnPreparing()
event because the SharedConstructorParameter
instance will be the cache of values per resolve operation.
class SharedConstructorParameter : Parameter
{
object _cachedInstance;
Type[] _sharedParameterTypes;
public SharedConstructorParameter(params Type[] sharedParameterTypes)
{
_sharedParameterTypes = sharedParameterTypes;
}
protected override bool CanSupplyValue(
ParameterInfo pi,
IComponentContext cc,
out Func<object> valueCreator)
{
valueCreator = null;
if (!_sharedParameterTypes.Contains(pi.ParameterType))
return false;
valueCreator = () => {
_cachedInstance = _cachedInstance ?? cc.Resolve(pi.ParameterType);
return cachedInstance;
};
return true;
}
}
Yours to compile and debug ;)