我正在使用Autofac构造函数注入。我需要弄清楚如何将单个对象实例注入多个构造函数参数,而无需在容器设置阶段显式解析每个参数。
我有一个复杂的场景,可以通过这种行为简化;以下示例只是一个简化的场景,因此我可以演示我正在寻找的行为。
示例:
说我有这两个接口,IOpenable和ICloseable:
public interface IOpenable
{
void Open();
}
public interface ICloseable
{
void Close();
}
我有这个Door类实现了它们:
public interface IDoor : IOpenable, ICloseable { }
public class Door : IDoor, IOpenable, ICloseable
{
void Open() { ... }
void Close() { ... }
}
我有这个类接受IOpenable和ICloseable:
public class DoorHandler : IDoorHandler
{
public DoorHandler(IOpenable openable, ICloseable closeable)
{
...
}
...
}
问题:
当IOpenable和ICloseable都是同一个构造函数中的依赖项时,是否可以告诉autofac将相同的 Door对象注入到两个参数中?
注意:我做不到:
container.Register<IDoorHandler>( c => {
Door d = c.Resolve<IDoor>();
return new DoorHandler(d,d)
});
将做我想做的事,但请记住,这个DoorHandler类只是一个例子。在我的实际代码中,“DoorHandler”实际上是一个MVC控制器,我正在使用RegisterControllers()进行注册。所以我不能像上面那样注册它。此外,有时依赖图会变得过于复杂,并且在每种情况下明确地执行此操作都会变得势不可挡。
我想我正在寻找的是能够做到这样的事情:
container.RegisterType<DoorHandler>().As<IDoorHandler>();
container.RegisterType<Door>().As<IDoor>();
container.Register<IOpenable>( c => c.ResolveShared<IDoor>(); );
container.Register<ICloseable>( c => c.ResolveShared<IDoor>(); );
如果在同一个构造函数中调用了多个参数,那么对c.ResolveShared<T>
的调用将全部解析为同一个T对象。
或者也许:
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>(); );
显然,如果我使用InstancePerLifetimeScope或Door对象的东西,每个已解析的门将是同一个实例。但是我不希望这样,每次创建一个DoorHandler时我想要一个新的Door实例,我希望将Door作为两个参数传递给DoorHandler构造函数。
答案 0 :(得分:3)
好的狡猾的一个:) ...这是广义的“每个构造函数”共享的一种可能的解决方案:
builder.RegisterControllers(asm)
.OnPreparing(e => {
var spr = new SharedConstructorParameter(
typeof(IOpenable),
typeof(ICloseable));
e.Parameters = new Parameter[]{ spr }.Concat(e.Parameters);
});
需要在OnPreparing()
事件中设置参数,因为SharedConstructorParameter
实例将是每个解析操作的值的缓存。
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;
}
}
您的编译和调试;)
答案 1 :(得分:2)
现在最接近Autofac的是将事情注册为InstancePerLifetimeScope。但是,如果您拥有的特定用例是MVC控制器,那么这可能就足够了。
使用Autofac的ASP.NET集成,每个传入的HTTP请求都有自己的生命周期范围,所以如果你有这个......
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly);
// Under the covers, this is really doing...
// builder.RegisterType<DoorController>().InstancePerHttpRequest();
InstancePerHttpRequest是一个类似于InstancePerLifetimeScope的扩展。围绕HTTP请求创建了一个新的生命周期范围,并在最后处置。
然后您可以将您的shared-lifetime-objects注册为InstancePerHttpRequest:
builder.RegisterType<Door>().As<IDoor>().InstancePerHttpRequest();
builder.RegisterType<Openable>().As<IOpenable>();
builder.RegisterType<Closeable>().As<ICloseable>();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
现在,当你的控制器被解析后,IDoor将是IOpenable和ICloseable实例中的同一个实例。
如果您不在请求范围内,那么您可以做的最好的事情就是:
var builder = new ContainerBuilder();
builder.RegisterType<DoorHandler>().As<IDoorHandler>();
builder.RegisterType<Door>().As<IDoor>().InstancePerLifetimeScope();
builder.RegisterType<Openable>().As<IOpenable>();
builder.RegisterType<Closeable>().As<ICloseable>();
var container = builder.Build();
将“共享”项目注册为InstancePerLifetimeScope。然后,当您需要解决时,您可以执行以下操作:
using(var lifetime = container.BeginLifetimeScope())
{
var dh = lifetime.Resolve<IDoorHandler>();
// The IDoor will be the same in both references here.
}
你可以在技术上将引用程序放在using语句之外,但是如果你的IDoor实现是一次性的,它们将在使用结束时与生命期范围一起处理,所以要小心。