Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac resolves components with mixed scope

I'm using Autofac 2.5 in asp.net and I'm having an issue where lifetime scope components are getting resolved as dependencies of single instance components thus destroying my thread safety. This is a problem with the registrations but I thought Autofac viewed this as a violation and would throw an exception.

    private class A{}

    private class B
    {
        public B(A a){}
    }

    [Test]
    [ExpectedException()]
    public void SingleInstanceCannotResolveLifetimeDependency()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<A>()
            .InstancePerLifetimeScope();
        builder.RegisterType<B>()
            .SingleInstance();

        using (var container = builder.Build())
        {
            using (var lifetime = container.BeginLifetimeScope())
            {
                //should throw an exception
                //because B is scoped singleton but A is only scoped for the lifetime
                var b = lifetime.Resolve<B>();
            }
        }
    }

Is there a way to have Autofac throw a Dependency resolution exception if this happens?

UPDATE Even though this is the correct behavior for Autofac - SingleInstance is just Root lifetime scoped - it can be potentially dangerous in a web environment. Making sure that all of your developers get the correct registrations can be a pain. Here is a small extension method for Autofac that checks an instance lookup to make sure that lifetime scoped instances do not get resolved in the root scope. I know it has helped us weed lifecycle issues out of our web project.

    public static class NoLifetimeResolutionAtRootScopeExtensions
{
    /// <summary>
    /// Prevents instances that are lifetime registration from being resolved in the root scope
    /// </summary>
    public static void NoLifetimeResolutionAtRootScope(this IContainer container)
    {
        LifetimeScopeBeginning(null, new LifetimeScopeBeginningEventArgs(container));
    }

    private static void LifetimeScopeBeginning(object sender, LifetimeScopeBeginningEventArgs e)
    {
        e.LifetimeScope.ResolveOperationBeginning += ResolveOperationBeginning;
        e.LifetimeScope.ChildLifetimeScopeBeginning += LifetimeScopeBeginning;
    }

    private static void ResolveOperationBeginning(object sender, ResolveOperationBeginningEventArgs e)
    {
        e.ResolveOperation.InstanceLookupBeginning += InstanceLookupBeginning;
    }

    private static void InstanceLookupBeginning(object sender, InstanceLookupBeginningEventArgs e)
    {
        var registration = e.InstanceLookup.ComponentRegistration;
        var activationScope = e.InstanceLookup.ActivationScope;

        if (registration.Ownership != InstanceOwnership.ExternallyOwned
            && registration.Sharing == InstanceSharing.Shared
            && !(registration.Lifetime is RootScopeLifetime)
            && activationScope.Tag.Equals("root"))
        {
            //would be really nice to be able to get a resolution stack here
            throw new DependencyResolutionException(string.Format(
                "Cannot resolve a lifetime instance of {0} at the root scope.", registration.Target))
        }
    }
}

Just apply this when you create your container and you will get exceptions thrown when lifetime scoped services are resolved in the root scope.

container.NoLifetimeResolutionAtRootScope();
like image 227
Danielg Avatar asked Oct 24 '22 20:10

Danielg


1 Answers

Yes - you need to name the child scope, and associate the component A with it explicitly. Otherwise, as you observe, a A instance is created in the root (container) scope.

// Replace `A` registration with:
builder.RegisterType<A>().InstancePerMatchingLifetimeScope("child");

And...

// Replace scope creation with:
using (var lifetime = container.BeginLifetimeScope("child")) {
like image 68
Nicholas Blumhardt Avatar answered Oct 26 '22 18:10

Nicholas Blumhardt