6

I'd like to have an instance per matching lifetime scoped registration in Autofac, but occasionally need to request an instance from a global container (where there is no matching lifetime scope). In scenarios where no matching lifetime scope exists, I want to give a top-level instance instead of throwing an exception.

Is this possible?

Charles
  • 50,943
  • 13
  • 104
  • 142
David Pfeffer
  • 38,869
  • 30
  • 127
  • 202
  • Why do you need this? Do you want the global registration to be differ from the scope registration? Or do you want consumers to get one instance in one case and other instances in other cases, while the configurations remain the same both for the global scope and for tagged scope? – Pavel Gatilov Feb 05 '13 at 17:49
  • @PavelGatilov The latter. Same configuration, different instance per tagged scope and different instance for global scope. – David Pfeffer Feb 05 '13 at 20:16
  • See https://stackoverflow.com/a/55394197/545863 for a clever implementation. – Vincent Mar 28 '19 at 11:01

3 Answers3

10

I think you'd better extend Autofac by introducing a new lifetime option. I took the Autofac sources and modified them a bit:

public static class RegistrationBuilderExtensions
{
    public static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> InstancePerMatchingOrRootLifetimeScope<TLimit, TActivatorData, TRegistrationStyle>(this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> builder, params object[] lifetimeScopeTag)
    {
        if (lifetimeScopeTag == null) throw new ArgumentNullException("lifetimeScopeTag");
        builder.RegistrationData.Sharing = InstanceSharing.Shared;
        builder.RegistrationData.Lifetime = new MatchingScopeOrRootLifetime(lifetimeScopeTag);
        return builder;
    }
}

public class MatchingScopeOrRootLifetime: IComponentLifetime
{
    readonly object[] _tagsToMatch;

    public MatchingScopeOrRootLifetime(params object[] lifetimeScopeTagsToMatch)
    {
        if (lifetimeScopeTagsToMatch == null) throw new ArgumentNullException("lifetimeScopeTagsToMatch");

        _tagsToMatch = lifetimeScopeTagsToMatch;
    }

    public ISharingLifetimeScope FindScope(ISharingLifetimeScope mostNestedVisibleScope)
    {
        if (mostNestedVisibleScope == null) throw new ArgumentNullException("mostNestedVisibleScope");

        var next = mostNestedVisibleScope;
        while (next != null)
        {
            if (_tagsToMatch.Contains(next.Tag))
                return next;

            next = next.ParentLifetimeScope;
        }

        return mostNestedVisibleScope.RootLifetimeScope;
    }
}

Just add these classes to your project and register you component as:

builder.RegisterType<A>.InstancePerMatchingOrRootLifetimeScope("TAG");

I haven't tried it myself, but it should work.

Nodios
  • 439
  • 6
  • 18
Pavel Gatilov
  • 7,579
  • 1
  • 27
  • 42
2

Possible solution is to override registration in child lifetime scope.

Sample:

public enum Scopes
{
    TestScope
}

public class Test
{
   public string Description { get; set; }
}

public class Tester
{
    public void DoTest()
    {
        ContainerBuilder builder = new ContainerBuilder();
        builder.RegisterType<Test>()
            .OnActivating(args => args.Instance.Description = "FromRoot")
            .SingleInstance();
        var container = builder.Build();

        var scope = container.BeginLifetimeScope(Scopes.TestScope, b => b
            .RegisterType<Test>()
            .InstancePerMatchingLifetimeScope(Scopes.TestScope)
            .OnActivating(args => args.Instance.Description = "FromScope"));

        var test1 = container.Resolve<Test>();
        Console.WriteLine(test1.Description); //writes FromRoot

        var test2 = scope.Resolve<Test>();
        Console.WriteLine(test2.Description); //writes FromScope

        Console.ReadLine();
    }
}
Memoizer
  • 2,231
  • 1
  • 14
  • 14
  • While the code is a little confusing, @Memoizer is right - the only way to do this is to override the registration in the child scope. Unfortunately what the question asks is "I want one lifetime scope style in one place and a different style in a different place" which isn't a supported scenario so it requires some custom code like this to make it happen. – Travis Illig Feb 05 '13 at 16:36
1

The root container itself is a lifetime scope with the tag name: root, defined in LifetimeScope.RootTag

So you can just do:

using Autofac.Core.Lifetime;

builder.RegisterType<Service>().As<IService>()
    .InstancePerMatchingLifetimeScope(LifetimeScope.RootTag, "foobar");
Shahin Dohan
  • 6,149
  • 3
  • 41
  • 58