2

I'm try to supply a different service to some tagged lifetime scopes in AutoFac, but can't seem to get the hang of it.

I've tried using the custom lifetime from Instance per matching lifetime scope, with default? but that didn't work.

I've written a test that illustrates what I'm trying to do:

[TestMethod]
public void NamedLifetimeTests()
{
    var builder = new ContainerBuilder();
    builder.Register(c => new ChildA()).As<Parent>().InstancePerLifetimeScope();
    builder.Register(c => new ChildB()).As<Parent>().InstancePerMatchingLifetimeScope("system").PreserveExistingDefaults();

    var container = builder.Build();
    using (var scope = container.BeginLifetimeScope())
    {
        var parent = scope.Resolve<Parent>();
        Assert.IsTrue(parent.GetType() == typeof(ChildA));
    }

    using (var scope = container.BeginLifetimeScope("system"))
    {
        var parent = scope.Resolve<Parent>();
        Assert.IsTrue(parent.GetType() == typeof(ChildB));
    }
}

Is this possible?

Vincent
  • 1,119
  • 11
  • 25

2 Answers2

4

The solution could be to provide these custom services for the lifetime scope when you create the scope. When creating a new lifetime scope with container.BeginLifetimeScope you can provide additional Action<ContainerBuilder> parameter to define some custom registrations for this particular lifetime scope.

So, for your code, instead of global registration for ChildB, you could move it to per-scope registration. It could look something like that:

[TestMethod]
public void NamedLifetimeTests()
{
    var builder = new ContainerBuilder();
    builder.Register(c => new ChildA()).As<Parent>().InstancePerLifetimeScope();    

    var container = builder.Build();
    using (var scope = container.BeginLifetimeScope())
    {
        var parent = scope.Resolve<Parent>();
        Assert.IsTrue(parent.GetType() == typeof(ChildA));
    }

    using (var scope = container.BeginLifetimeScope("system"), cb => { cb.RegisterType<ChildB>().As<Parent>(); }))
    {
        var parent = scope.Resolve<Parent>();
        Assert.IsTrue(parent.GetType() == typeof(ChildB));
    }
}

EDIT: Avoiding injecting lifetime scope is understandable. Another solution could be picking proper implementation, based on lifetime scope tag, similar to selection of implementation based on parameter, described in docs:

// omitted ...
var builder = new ContainerBuilder();
builder.Register<Parent>(c =>
    {
        var currentScope = c.Resolve<ILifetimeScope>();
        if (currentScope.Tag?.ToString() == "system")
        {
            return new ChildB();
        }

        return new ChildA();
    }).InstancePerLifetimeScope();   

var container = builder.Build();
// omitted ...
tdragon
  • 3,209
  • 1
  • 16
  • 17
  • I'm aware of this construct, and I'm sorry for not mentioning this in the post, but I do not control the creation of the lifetimescope. I could use the approach you are suggesting by injecting the lifetimescope and immediately create a new nested lifetimescope within the class with the specified tag, but I would like to avoid injecting the lifetimescope as per the best-practices recommendations. – Vincent Mar 28 '19 at 09:34
  • 1
    I've thought of the approach in your edit, but I could not find any Register function with a lambda exposing the ILifetimeScope. Resolving it was brilliant and I did not think of that at all! Thank you very much! I'll have to do some tests to see how this works within untagged scopes created inside the tagged scope. – Vincent Mar 28 '19 at 10:59
  • Happy to help :) I have run some basic tests only, if there are any issues with nested lifetime scopes, let me know – tdragon Mar 28 '19 at 11:02
  • It does not quite work with nested scopes, and I cannot tag them with the parent's tag either. Any suggestions tdragon? – Vincent Apr 13 '19 at 19:36
0

The following extension can be used to automate tdragon's answer. It also solves the nested scope problem.

public static class RegistrationBuilderExtensions
{
    public static IRegistrationBuilder<TLimit, SimpleActivatorData, SingleRegistrationStyle>
        RegisterScopeResolver<TLimit>(this ContainerBuilder containerBuilder)
    {
        IRegistrationBuilder<TLimit, SimpleActivatorData, SingleRegistrationStyle> defineServiceScopes =
            containerBuilder.Register(componentContext =>
            {
                var scope = componentContext.Resolve<ILifetimeScope>();
                var registrations = componentContext.ComponentRegistry.RegistrationsFor(new TypedService(typeof(TLimit))).ToList();
                Type activatorLimitType = registrations.Where(x => x.Lifetime is MatchingScopeLifetime).SingleOrDefault(x =>
                        (x.Lifetime as MatchingScopeLifetime).TagsToMatch.Contains(scope.Tag))?.Activator.LimitType;

                if (activatorLimitType == null)
                {
                    activatorLimitType = registrations.Single(x =>
                        {
                            return x.Lifetime.GetType() != typeof(MatchingScopeByFirstOccurenceLifetime<TLimit>) &&
                                   x.Lifetime.GetType() != typeof(MatchingScopeLifetime);
                        })
                        .Activator
                        .LimitType;
                }
                return (TLimit) componentContext
                    .Resolve(activatorLimitType);
            });
        defineServiceScopes.RegistrationData.Sharing = InstanceSharing.Shared;
        defineServiceScopes.RegistrationData.Lifetime = new MatchingScopeByFirstOccurenceLifetime<TLimit>();
        return defineServiceScopes;
    }

    private class MatchingScopeByFirstOccurenceLifetime<TLimit> : IComponentLifetime
    {
        public ISharingLifetimeScope FindScope(ISharingLifetimeScope mostNestedVisibleScope)
        {
            if (mostNestedVisibleScope == null)
                throw new ArgumentNullException(nameof(mostNestedVisibleScope));

            var next = mostNestedVisibleScope;
            while (next != null)
            {
                if (next.ComponentRegistry
                    .RegistrationsFor(new TypedService(typeof(TLimit)))
                    .Select(x => x.Lifetime)
                    .OfType<MatchingScopeLifetime>()
                    .Any(x => x.TagsToMatch.Contains(next.Tag)))
                    return next;
                next = next.ParentLifetimeScope;
            }
            return mostNestedVisibleScope.RootLifetimeScope;
        }
    }
}

Usage

ContainerBuilder containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<BaseA>().As<IMyBase>().InstancePerMatchingLifetimeScope("A").AsSelf();
containerBuilder.RegisterType<BaseB>().As<IMyBase>().InstancePerMatchingLifetimeScope("B").AsSelf();
containerBuilder.RegisterScopeResolver<IMyBase>();

var container = containerBuilder.Build();
using (var lifetimeScope = container.BeginLifetimeScope("A"))
{
    Console.WriteLine(lifetimeScope.Resolve<IMyBase>());
    using (var nestedLifetimeScope = lifetimeScope.BeginLifetimeScope())
    {
        Console.WriteLine(nestedLifetimeScope.Resolve<IMyBase>());
    }
}

using (var lifetimeScope = container.BeginLifetimeScope("B"))
{
    Console.WriteLine(lifetimeScope.Resolve<IMyBase>());
    using (var nestedLifetimeScope = lifetimeScope.BeginLifetimeScope())
    {
        Console.WriteLine(nestedLifetimeScope.Resolve<IMyBase>());
    }
}

Result

BaseA Id:69fc4258-6920-42a6-8404-94d11ce46064
BaseA Id:69fc4258-6920-42a6-8404-94d11ce46064
BaseB Id:53d45071-5810-4358-ae87-bf250f5e3f02
BaseB Id:53d45071-5810-4358-ae87-bf250f5e3f02
Pepernoot
  • 3,409
  • 3
  • 21
  • 46