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

发布于 2024-10-27 04:46:00 字数 2475 浏览 7 评论 0原文

我正在使用 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)
    {
        ...
    }
    ...
}

问题:

是否可以告诉 autofac 将相同 Door 对象注入到两个参数中,只要两个参数都为IOpenable 和 ICloseable 是同一个构造函数中的依赖项吗?

注意:我做不到

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 对象构造函数。

或者也许:

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 时都有一个 DoorHandler 的新实例,并且我希望该 Door 作为两个参数传递给 DoorHandler 构造函数。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

欢你一世 2024-11-03 04:46:00

好吧,棘手的一个:)...这是通用“每个构造函数”共享的一种可能的解决方案:

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

由您来编译和调试;)

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

作业与我同在 2024-11-03 04:46:00

目前使用 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 实现是一次性的,它们将在最后与生命周期范围一起被处置的使用,所以要小心。

The closest you'll get with Autofac as it stands today is to register things as InstancePerLifetimeScope. However, that may be enough if the specific use case you have is an MVC controller.

With Autofac's ASP.NET integration, every incoming HTTP request has its own lifetime scope, so if you have this...

var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly);
// Under the covers, this is really doing...
// builder.RegisterType<DoorController>().InstancePerHttpRequest();

That InstancePerHttpRequest is an extension similar to InstancePerLifetimeScope. A new lifetime scope is created around your HTTP request and disposed at the end.

Then you can register your shared-lifetime-objects also as 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));

And now when your controller is resolved, the IDoor will be the same instance in both the IOpenable and ICloseable instances.

If you are outside of a request scope, the best you can do is something like:

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();

Register the "shared" items as InstancePerLifetimeScope. Then when you need to resolve, you can do something like:

using(var lifetime = container.BeginLifetimeScope())
{
  var dh = lifetime.Resolve<IDoorHandler>();
  // The IDoor will be the same in both references here.
}

You could technically put the reference to the door handler outside the using statement, but then if your IDoor implementations are disposable, they'll get disposed along with the lifetime scope at the end of the using, so be careful with that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文