Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
jdh28 opened this issue Oct 9, 2024 · 3 comments

Comments

@jdh28
Copy link

jdh28 commented Oct 9, 2024

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
Copy link
Member

tillig commented Oct 9, 2024

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
Copy link
Author

jdh28 commented Oct 11, 2024

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);
    }
  • We start initializing the ServiceRegistrationInfo for TypeWithInternalCtor in GetInitializedServiceInfo().
  • When it starts configuring the pipeline in BeginServiceInfoInitialization the ExternalRegistryServiceMiddlewareSource wraps the root container, not the lifetime scope so any registrations ignore the scope.
  • Building the pipeline causes re-entry into GetInitializedServiceInfo(), but now in the root container, not the scope.
  • In this inner call to GetInitializedServiceInfo() we iterate through the available sources, which includes ACTNARS.
  • ACTNARS then can't find any other registrations in the root container, so generates a registration that fails, because there are no public ctors.

@jdh28
Copy link
Author

jdh28 commented Oct 11, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants