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.