How to add two instances of the same object using Autofac?

I am using the constructor constructor of Autofac. I need to figure out how to insert an instance of one object into several constructor arguments, without having to explicitly resolve each argument during the container configuration phase.

I have a complex scenario that will be simplified by this behavior; The following example is just a simplified scenario, so I can demonstrate the behavior I'm looking for.

Example:

Let's 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 that implements both of them:

 public interface IDoor : IOpenable, ICloseable { } public class Door : IDoor, IOpenable, ICloseable { void Open() { ... } void Close() { ... } } 

And I have this class that accepts IOpenable and ICloseable:

 public class DoorHandler : IDoorHandler { public DoorHandler(IOpenable openable, ICloseable closeable) { ... } ... } 

Question:

Is it possible to tell autofac to insert the same Door object into both arguments when both IOpenable and ICloseable are dependencies in the same constructor?

Note. I can not do :

 container.Register<IDoorHandler>( c => { Door d = c.Resolve<IDoor>(); return new DoorHandler(d,d) }); 

This will do what I want, but remember that this DoorHandler class is just an example. In my real code, the β€œDoorHandler” is indeed an MVC controller, and I am registering it with RegisterControllers (). Therefore, I cannot register it as indicated above. In addition, sometimes dependency graphs can be overly complex, and this clearly can be overwhelming in each case.

I assume that I am looking 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>(); ); 

when calls to c.ResolveShared<T> will be resolved to the same T object if more than one argument is called in the 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 used InstancePerLifetimeScope or something for the Door object, each resolved Door would be the same instance. But I do not want this, I want each DoorHandler instance to create a new door instance, and I want this Door to be passed as both arguments to the DoorHandler constructor.

+4
source share
2 answers

One good thing is complicated: ... Here is one possible solution for a generalized exchange for "constructor":

 builder.RegisterControllers(asm) .OnPreparing(e => { var spr = new SharedConstructorParameter( typeof(IOpenable), typeof(ICloseable)); e.Parameters = new Parameter[]{ spr }.Concat(e.Parameters); }); 

The parameter must be configured in the OnPreparing() event, since the SharedConstructorParameter instance will be a value cache for each resolution 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; } } 

For compilation and debugging;)

+3
source

Closest you will get Autofac as it stands today to register things as InstancePerLifetimeScope. However, this may be sufficient if you have a specific MVC use case.

Thanks to Autofac ASP.NET integration, each incoming HTTP request has its own lifetime, so if you have this ...

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

This InstancePerHttpRequest instance is an extension similar to the InstancePerLifetimeScope instance. A new area of ​​life is created around your HTTP request and is located at the end.

Then you can register your total lifetime objects as well 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 that your controller is enabled, IDoor will be the same instance in both IOpenable and ICloseable instances.

If you are outside the scope of the request, 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 "generic" elements as InstancePerLifetimeScope. Then, when you need to decide, 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 can technically put a link to the door handler outside the using statement, but then if your IDoor implementations are one-time, they will be deleted along with the scope at the end of use, so be careful with this.

+2
source

Source: https://habr.com/ru/post/1345114/


All Articles