I can not figure out the documentation Autofac, on the subject of a manual task scope for dependence.

//КОНСТРУКТОРЫ ФОРМ ctor ScopeForm_1(Func<ScopeChildForm_1> childForm1Factory, Owned<ILogger> logger); ctor ScopeChildForm_1(ILogger logger); ctor ScopeForm_2(Func<ScopeChildForm_2> childForm2Factory, Owned<ILogger> logger); ctor ScopeChildForm_2(ILogger logger); //РЕГИСТРАЦИЯ builder.RegisterType<NyLogger>().As<ILogger>().InstancePerLifetimeScope(); builder.RegisterType<ScopeForm_1>().InstancePerDependency(); builder.RegisterType<ScopeForm_2>().InstancePerDependency(); builder.RegisterType<ScopeChildForm_1>().InstancePerDependency(); builder.RegisterType<ScopeChildForm_2>().InstancePerDependency(); 

If you register in the global scope, then ScopeChildForm_1 and ScopeChildForm_2 will receive one and the same logger, for ScopeForm_1 - its own instance of logger, for ScopeForm_2 - its own instance of logger.

I understand that when I register all the services I register them in the global scope (and set the lifetime to this scope)

  var builder = new ContainerBuilder(); builder.RegisterType<NyLogger>().As<ILogger>().InstancePerDependency(); builder.RegisterType<NyLogger>().As<ILogger>().InstancePerLifetimeScope(); 

InstancePerDependency - every time a new object is created. InstancePerLifetimeScope is the same object for the whole program as Singleton, since the entire rezolv will come from one scopa (GLOBAL), except for the Owned wrapper, it also creates a new object with manual control of the lifetime.

I NEED TO: For ScopeForm_1 and for ScopeChildForm_1, one logger is created in its scope. Also ScopeForm_1 controls the lifetime of this logger, through Owned. For ScopeForm_2 a similar pair only in a different scope

  • I understand that using IOwner I create a scope wrapper and by calling Dispose () I try to destroy the service and all its dependencies, if the dependencies are not Singleton they will be destroyed. In the example above, if I close ScopeForm_1, I want to destroy the ILogger, when I open the form, I want to create an ILogger, when I open the children of the form ScopeChildForm_1, I want to transfer the ILogger already created in ScopeForm_1. - Aldmi
  • Oh, did not even notice "Owned" of course, I apologize - Aldmi
  • Corrected a question - Aldmi

1 answer 1

The main problem is that you cannot control the lifetime of the ILogger and the ILogger dependent on it independently! What happens if you release the ILogger while some ScopeChildForm_1 still exist? Therefore, the first thing to do is to prepare a common container for ILogger and the factories of all child forms:

 public class WithLogger<T> { private Func<T> Factory { get; } public ILogger Logger { get; } public WithLogger(Func<T> factory, ILogger logger) { Factory = factory; Logger = logger; } public T Create() => Factory(); } 

Well, then everything is simple:

 builder.RegisterGeneric(typeof(WithLogger<>)).ExternallyOwned(); builder.RegisterType<Logger>().As<ILogger>().InstancePerMatchingLifetimeScope(new TypedService(typeof(WithLogger<ScopeChildForm_1>)), new TypedService(typeof(WithLogger<ScopeChildForm_2>))); // ... public ScopeForm_1(Owned<WithLogger<ScopeChildForm_1>> childForm1Factory); public ScopeForm_2(Owned<WithLogger<ScopeChildForm_2>> childForm2Factory); 

I note that if the form was only one, then it would be possible to make it a bit simpler:

 builder.RegisterType<Logger>().As<ILogger>().InstancePerOwned<WithLogger<ScopeChildForm>>(); 

If it seems that there is too much magic in all of them - it can be done like this. First, change the container to get rid of Owned:

 public interface ILifetimeScopeOwner : IDisposable { event Action Disposed; } public class WithLogger<T> : ILifetimeScopeOwner { private Func<T> Factory { get; } public ILogger Logger { get; } public event Action Disposed; public WithLogger(Func<T> factory, ILogger logger) { Factory = factory; Logger = logger; } public T Create() => Factory(); public void Dispose() => Disposed?.Invoke(); } 

Next you need a way to create such containers in separate scopes. To do this, you have to have a separate source of registration The job description of this just pulls in on a small article, so I just quote the code with comments:

 /// <summary> /// Источник регистраций для обобщенных контейнеров, позволяющий им работать как Owned (т.е. создавать свой вложенный scope и управлять им) /// Источник вдохвовения: исходники класса Autofac.Features.OwnedInstances.OwnedInstanceRegistrationSource /// </summary> class OwnedContainerSource : IRegistrationSource { private readonly Type definition; private readonly object tag; public OwnedContainerSource(Type definition, object tag) { this.definition = definition; this.tag = tag; if (!typeof(ILifetimeScopeOwner).IsAssignableFrom(definition)) throw new ArgumentException("must be assignable to ILifetimeScopeOwner", "definition"); } public bool IsAdapterForIndividualComponents => true; public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) { TypedService ts = service as TypedService; if (ts == null || ts.ServiceType != definition && (!ts.ServiceType.IsGenericType || ts.ServiceType.GetGenericTypeDefinition() != definition)) yield break; // Ищем "внутреннюю" регистрацию. Это делается для того чтобы не создавать объект вручную. var innerRegistration = registrationAccessor(new KeyedService("inner", ts.ServiceType)).FirstOrDefault(); if (innerRegistration == null || innerRegistration.Ownership != InstanceOwnership.ExternallyOwned) throw new NotSupportedException("inner registration must exist and be ExternallyOwned"); // Все ок, можно создавать запрошенную регистрацию yield return RegistrationBuilder.ForDelegate(ts.ServiceType, (ctx, @params) => { // Создаем вложенный скоуп, а в нем - резолвим объект var scope = ctx.Resolve<ILifetimeScope>().BeginLifetimeScope(tag); var container = (ILifetimeScopeOwner)scope.ResolveComponent(innerRegistration, @params); container.Disposed += scope.Dispose; return container; }).Targeting(innerRegistration).ExternallyOwned().CreateRegistration(); } } 

Well, then - again, everything is quite simple:

 builder.RegisterSource(new OwnedContainerSource(typeof(WithLogger<>), "form")); // Источник для "внешних" регистраций, создает отдельный скоуп с именем form на каждый запрос builder.RegisterGeneric(typeof(WithLogger<>)).Named("inner", typeof(WithLogger<>)).ExternallyOwned(); // Внутренняя регистрация, выполняется уже в новом скоупе builder.RegisterType<Logger>().As<ILogger>().InstancePerMatchingLifetimeScope("form"); public ScopeForm_1(WithLogger<ScopeChildForm_1> childForm1Factory); public ScopeForm_2(WithLogger<ScopeChildForm_2> childForm2Factory); 

Pay attention to the string constants "inner" and "form". The first is used to find the "internal" registration, the second - as a marker for lifetime scope. There is nothing magical about them; you can use any other lines or even objects.


Another direction where you can dig is its implementation of the IComponentLifetime interface. But this is a non-canonical direction, I did not see anyone doing this.

  • Thank. I will train. Tell ScopeForm_1 and ScopeChildForm_1 now depends on WithLogger <ILogger>? or the factory is called by the automaton and the dependencies are simple on the ILogger. - Aldmi
  • Of course they depend on the ILogger and not on the factory! - Pavel Mayorov