如何使用Autofac注入同一对象的两个实例?

时间:2011-03-24 15:44:48

标签: autofac constructor-injection

我正在使用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构造函数。

2 个答案:

答案 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实现是一次性的,它们将在使用结束时与生命期范围一起处理,所以要小心。