How to inject two instances of same object using Autofac?

最后都变了- 提交于 2019-12-10 09:19:24

问题


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.


回答1:


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




回答2:


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.



来源:https://stackoverflow.com/questions/5421803/how-to-inject-two-instances-of-same-object-using-autofac

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!