4

我正在使用 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>如果在同一个构造函数中调用多个参数,where 调用将全部解析为同一个 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>(); );

显然,如果我对 Door 对象使用 InstancePerLifetimeScope 或其他东西,每个已解析的 Door 都将是同一个实例。但我不希望这样,每次创建 DoorHandler 时我都想要一个 Door 的新实例,并且我希望将该 Door 作为两个参数传递给 DoorHandler 构造函数。

4

2 回答 2

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;
    }
}

你的编译和调试;)

于 2011-03-25T04:39:00.710 回答
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 请求创建一个新的生命周期范围,并在最后处理。

然后您可以将您的共享生命周期对象也注册为 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 实现是一次性的,它们将在使用结束时与生命周期范围一起被处置,因此请注意这一点。

于 2011-03-25T00:11:42.487 回答