autofac / Autofac

An addictive .NET IoC container
https://autofac.org
MIT License
4.5k stars 835 forks source link

Resolving an externally provided component from a lifetime scope still checks for public constructors #1433

Open jdh28 opened 1 month ago

jdh28 commented 1 month ago

Describe the Bug

I have an object instance that I'm providing when creating a lifetime scope. The type's constructor is internal. When trying to resolve a component that depends on this type, I get an exception because the type has no public constructors. However, an instance of the type has been provided so there should be no need to examine its constructors.

The AnyConcreteTypeNotAlreadyRegisteredSource source needs to be registered to trigger this.

Interestingly, if you don't use a scope and do all the registrations in the container root, then the resolve succeeds.

I've encountered this issue upgrading a large application from v4.9.2 to v8.1.0. The change in behaviour started in v6.0.

Steps to Reproduce

public class ExternalDependency
{
    public int Value { get; }

    internal ExternalDependency(int value) => Value = value;
}

public class Service1
{
    private readonly ExternalDependency _dependency;

    public Service1(ExternalDependency dependency)
    {
        _dependency = dependency;
    }
}

[TestFixture]
public class ResolveTests
{
    [Test]
    public void ResolveExternalComponentInScope()
    {
        var builder = new ContainerBuilder();
        builder.RegisterSource<AnyConcreteTypeNotAlreadyRegisteredSource>();
        builder.RegisterType<Service1>().AsSelf().InstancePerMatchingLifetimeScope("scope1");

        var externalDependency = new ExternalDependency(42);
        using var container = builder.Build();
        using var scope = container.BeginLifetimeScope("scope1", b =>
        {
            b.RegisterInstance(externalDependency).AsSelf().AsImplementedInterfaces().ExternallyOwned();
        });

        Assert.That(() => scope.Resolve<Service1>(), Throws.Nothing);
    }
}

Expected Behavior

The call to Resolve to succeed and the instance of Service1 to be created with the instance of ExternalDependency passed into its constructor.

Exception with Stack Trace


Autofac.Core.DependencyResolutionException: An exception was thrown while activating AutofacTest.Service1. ---> Autofac.Core.Activators.Reflection.NoConstructorsFoundException: No constructors on type 'AutofacTest.ExternalDependency' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'.

See https://autofac.rtfd.io/help/no-constructors-found for more info.
   at Autofac.Core.Activators.Reflection.ReflectionActivator.ConfigurePipeline(IComponentRegistryServices componentRegistryServices, IResolvePipelineBuilder pipelineBuilder)
   at Autofac.Core.Registration.ComponentRegistration.BuildResolvePipeline(IComponentRegistryServices registryServices, IResolvePipelineBuilder pipelineBuilder)
   at Autofac.Core.Registration.ComponentRegistration.BuildResolvePipeline(IComponentRegistryServices registryServices)
   at Autofac.Core.Registration.DefaultRegisteredServicesTracker.AddRegistration(IComponentRegistration registration, Boolean preserveDefaults, Boolean originatedFromDynamicSource)
   at Autofac.Core.Registration.DefaultRegisteredServicesTracker.GetInitializedServiceInfo(Service service)
   at Autofac.Core.Registration.DefaultRegisteredServicesTracker.ServiceMiddlewareFor(Service service)
   at Autofac.Core.Registration.ComponentRegistry.ServiceMiddlewareFor(Service service)
   at Autofac.Core.Registration.ExternalRegistryServiceMiddlewareSource.ProvideMiddleware(Service service, IComponentRegistryServices availableServices, IResolvePipelineBuilder pipelineBuilder)
   at Autofac.Core.Registration.DefaultRegisteredServicesTracker.BeginServiceInfoInitialization(Service service, ServiceRegistrationInfo info, IEnumerable`1 registrationSources)
   at Autofac.Core.Registration.DefaultRegisteredServicesTracker.GetInitializedServiceInfo(Service service)
   at Autofac.Core.Registration.DefaultRegisteredServicesTracker.TryGetServiceRegistration(Service service, ServiceRegistration& serviceData)
   at Autofac.Core.Registration.ComponentRegistry.TryGetServiceRegistration(Service service, ServiceRegistration& serviceRegistration)
   at Autofac.Core.Activators.Reflection.AutowiringParameter.CanSupplyValue(ParameterInfo pi, IComponentContext context, Func`1& valueProvider)
   at Autofac.Core.Activators.Reflection.ConstructorBinder.Bind(IEnumerable`1 availableParameters, IComponentContext context)
   at Autofac.Core.Activators.Reflection.ReflectionActivator.<>c__DisplayClass14_0.<UseSingleConstructorActivation>b__0(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Middleware.DelegateMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
   at Autofac.Core.Resolving.Middleware.DisposalTrackingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
   at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   --- End of inner exception stack trace ---
   at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
   at Autofac.Core.Pipeline.ResolvePipeline.Invoke(ResolveRequestContext context)
   at Autofac.Core.Resolving.Middleware.RegistrationPipelineInvokeMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
   at Autofac.Core.Resolving.Middleware.SharingMiddleware.<>c__DisplayClass5_0.<Execute>b__0()
   at Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(Guid id, Func`1 creator)
   at Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(Guid primaryId, Nullable`1 qualifyingId, Func`1 creator)
   at Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
   at Autofac.Core.Resolving.Middleware.ScopeSelectionMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
   at Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
   at Autofac.Core.Pipeline.ResolvePipeline.Invoke(ResolveRequestContext context)
   at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest& request)
   at Autofac.Core.Resolving.ResolveOperation.ExecuteOperation(ResolveRequest& request)
   at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest& request)
   at Autofac.Core.Lifetime.LifetimeScope.Autofac.IComponentContext.ResolveComponent(ResolveRequest& request)
   at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
   at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
   at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters)
   at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context)
   at AutofacTest.ResolveTests.<>c__DisplayClass0_0.<ResolveExternalComponent>b__1() in c:\temp\projects\AutofacTest\Program.cs:line 41```

## Dependency Versions

Autofac: 8.1.0
The change in behaviour occurted at v6.0.0. Test test above will pass with v5.2.0 and earlier.
tillig commented 1 month ago

Yeah, a ton has changed since 4.9.2 was released back in early 2019. I also know ACTNARS has been no end of trouble, like registration order matters with it and it provides a lot of weird exceptions by effectively registering everything in the system. Basically.

I'm not sure we'll be able to "hop right on this." We'd love to see a PR for it, though.

jdh28 commented 1 month ago

I have had a look at this, but I'm struggling to understand the root cause. Just in case it is helpful to anyone who looks at this in the future, this is what I have discovered so far.

I reduced my test case further - the failure can be observed resolving what I called 'ExternalDependency' above directly.

    [Fact]
    public void IgnoresRegisteredInstancesWithInternalCtorWhenResolvingFromScope()
    {
        // issue 1433
        var cb = new ContainerBuilder();
        var internalCtor = new TypeWithInternalCtor();
        cb.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
        var container = cb.Build();

        using var scope = container.BeginLifetimeScope("scope1", b => b.RegisterInstance(internalCtor));
        var resolved = scope.Resolve<TypeWithInternalCtor>();
        Assert.Same(internalCtor, resolved);
    }
jdh28 commented 1 month ago

My best bet may be to explore getting rid of our need for using ACTNARS. It was probably very originally used out of laziness...