How to inject two instances of same object using Autofac?

岁酱吖の 提交于 2019-12-05 12:26:44
Nicholas Blumhardt

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

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.

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