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 services registered as open generics with generic constraints throws ComponentNotRegisteredException #972

Closed
ghen opened this issue Mar 27, 2019 · 5 comments

Comments

@ghen
Copy link

ghen commented Mar 27, 2019

Possibly related to #688.

Whenever registered open generic has another generic class or interface reference in its type constraint, service resolution fails with ComponentNotRegisteredException error.

Code to reproduce:

    private static void Main(String[] args) {

      var builder = new ContainerBuilder();
      builder.RegisterGeneric(typeof(GenericTypeClass<,>)).AsSelf();
      builder.RegisterGeneric(typeof(GenericCollectionClass<,>)).AsSelf();
      var container = builder.Build();

      // This would resolve nicely
      var resolved = container.Resolve<GenericTypeClass<StringContrainClassImp, String>>();

      // This would throw ComponentNotRegisteredException
      var notResolved = container.Resolve<GenericCollectionClass<CollectionContrainClassImp, String>>();

    }
  }

  public abstract class ConstraintClass<TInput> { }

  public sealed class GenericTypeClass<TConstraint, TInput> where TConstraint : ConstraintClass<TInput> { }
  public sealed class StringContrainClassImp : ConstraintClass<String> { }

  public sealed class GenericCollectionClass<TConstraint, TInput> where TConstraint : ConstraintClass<IEnumerable<TInput>> { }
  public sealed class CollectionContrainClassImp : ConstraintClass<IEnumerable<String>> {}

Exception details:

Unhandled Exception: Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'TestCore.GenericCollectionClass`2[[TestCore.CollectionContrainClassImp, TestCore, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
   at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 880
   at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 300
   at TestCore.Program.Main(String[] args) in C:\Temp\TestCore\Program.cs:line 18

Environment:

  • .NET Core 2.2 (x64)
  • Autofac, Version=4.9.1.0, Culture=neutral, PublicKeyToken=17863af14b0044da
@tillig
Copy link
Member

tillig commented Apr 1, 2019

I've added a failing unit test and traced this down as being in Autofac.Util.TypeExtensions.ParameterEqualsConstraint. This gets called when the open generic binder uses TypeExtensions.IsCompatibleWithGenericParameterConstraints to determine if the service being resolved matches something the open generic source can provide.

In ParameterEqualsConstraint the inbound parameter is the CollectionConstrainClassImp and the constraint is ConstraintClass<IEnumerable<TInput>>. Unfortunately, the method currently assumes that the inbound parameter is generic and has type arguments that need to be broken down and, if it doesn't, bails out saying the constraint has failed. We'll need to add something either in the ParameterCompatibleWithTypeConstraint or the ParameterEqualsConstraint methods to handle the case where the class provided isn't generic.

I'm curious if, in general, there's a better way to figure out through reflection if a given concrete type fulfills an open generic's constraints. If so, it would be better to employ something more robust.

I've soaked up all my time for the day drilling down into this to locate the problem; I won't be able to fix it immediately. If others have a fix/PR, I'm open to that.

@tillig
Copy link
Member

tillig commented Apr 9, 2019

This gets fixed by #978. Yay!

@tillig tillig closed this as completed Apr 9, 2019
@danjohnso
Copy link

I think this change broke Entity Framework Core DI registrations. Another change in the 4.9.3 release broke the app initially because of a duplicate registration that was mistakenly in the application. When I resolved that issue, there is now an error getting a DbContext. The registrations for DbContext are supposed to be handled by framework so I am not sure if this is an issue with something I am doing, Microsoft is doing, or an issue in this change. Curious if anyone else is encountering this, here is stack trace from my application:

Autofac.Core.DependencyResolutionException: An exception was thrown while activating λ:Microsoft.EntityFrameworkCore.Internal.DbContextDependencies -> Microsoft.EntityFrameworkCore.Internal.DbContextDependencies. ---> System.InvalidOperationException: This operation is only valid on generic types.
   at Type RuntimeType.GetGenericTypeDefinition()
   at bool Autofac.Util.TypeExtensions.ParameterCompatibleWithTypeConstraint(Type parameter, Type constraint) in C:\projects\autofac\src\Autofac\Util\TypeExtensions.cs:line 193
   at bool Autofac.Util.TypeExtensions.IsCompatibleWithGenericParameterConstraints(Type genericTypeDefinition, Type[] parameters)+(Type constraint) => { } in C:\projects\autofac\src\Autofac\Util\TypeExtensions.cs:line 83
   at bool System.Linq.Enumerable.Any<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
   at bool Autofac.Util.TypeExtensions.IsCompatibleWithGenericParameterConstraints(Type genericTypeDefinition, Type[] parameters) in C:\projects\autofac\src\Autofac\Util\TypeExtensions.cs:line 81
   at bool Autofac.Features.OpenGenerics.OpenGenericServiceBinder.TryBindServiceType(Service service, IEnumerable<Service> configuredOpenGenericServices, Type openGenericImplementationType, out Type constructedImplementationType, out Service[] constructedServices) in C:\projects\autofac\src\Autofac\Features\OpenGenerics\OpenGenericServiceBinder.cs:line 57
   at IEnumerable<IComponentRegistration> Autofac.Features.OpenGenerics.OpenGenericRegistrationSource.RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)+MoveNext()
   at ServiceRegistrationInfo Autofac.Core.Registration.ComponentRegistry.GetInitializedServiceInfo(Service service) in C:\projects\autofac\src\Autofac\Core\Registration\ComponentRegistry.cs:line 360
   at IEnumerable<IComponentRegistration> Autofac.Core.Registration.ComponentRegistry.RegistrationsFor(Service service) in C:\projects\autofac\src\Autofac\Core\Registration\ComponentRegistry.cs:line 251
   at IEnumerable<IComponentRegistration> Autofac.Core.Registration.ExternalRegistrySource.RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) in C:\projects\autofac\src\Autofac\Core\Registration\ExternalRegistrySource.cs:line 71
   at ServiceRegistrationInfo Autofac.Core.Registration.ComponentRegistry.GetInitializedServiceInfo(Service service) in C:\projects\autofac\src\Autofac\Core\Registration\ComponentRegistry.cs:line 360
   at bool Autofac.Core.Registration.ComponentRegistry.TryGetRegistration(Service service, out IComponentRegistration registration) in C:\projects\autofac\src\Autofac\Core\Registration\ComponentRegistry.cs:line 128
   at bool Autofac.Core.Activators.Reflection.AutowiringParameter.CanSupplyValue(ParameterInfo pi, IComponentContext context, out Func<object> valueProvider) in C:\projects\autofac\src\Autofac\Core\Activators\Reflection\AutowiringParameter.cs:line 55
   at new Autofac.Core.Activators.Reflection.ConstructorParameterBinding(ConstructorInfo ci, IEnumerable<Parameter> availableParameters, IComponentContext context) in C:\projects\autofac\src\Autofac\Core\Activators\Reflection\ConstructorParameterBinding.cs:line 86
   at ConstructorParameterBinding[] Autofac.Core.Activators.Reflection.ReflectionActivator.GetValidConstructorBindings(IComponentContext context, IEnumerable<Parameter> parameters) in C:\projects\autofac\src\Autofac\Core\Activators\Reflection\ReflectionActivator.cs:line 141
   at object Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable<Parameter> parameters) in C:\projects\autofac\src\Autofac\Core\Activators\Reflection\ReflectionActivator.cs:line 120
   at object Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable<Parameter> parameters, out object decoratorTarget) in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 117
   --- End of inner exception stack trace ---
   at object Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable<Parameter> parameters, out object decoratorTarget) in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 135
   at object Autofac.Core.Resolving.InstanceLookup.Execute() in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 83
   at object Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable<Parameter> parameters) in C:\projects\autofac\src\Autofac\Core\Resolving\ResolveOperation.cs:line 131
   at object Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable<Parameter> parameters) in C:\projects\autofac\src\Autofac\Core\Resolving\ResolveOperation.cs:line 84
   at bool Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable<Parameter> parameters, out object instance) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 1041
   at object Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable<Parameter> parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 871
   at object Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at T Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<T>(IServiceProvider provider)
   at IDbContextDependencies Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at IServiceProvider Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at IDbContextDependencies Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at IModel Microsoft.EntityFrameworkCore.DbContext.get_Model()
   at IEntityType Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.get_EntityType()
   at EntityQueryable<TEntity> Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.get_EntityQueryable()
   at IQueryProvider Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.System.Linq.IQueryable.get_Provider()
   at IQueryable<TSource> System.Linq.Queryable.Where<TSource>(IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

@tillig
Copy link
Member

tillig commented Jul 23, 2019

@danjohnso If you would, please open a new issue and include a repro that shows us how to see this happen. Unfortunately a stack trace is not enough to determine if this change is related or not, or to try to debug what you're seeing. Thanks!

@travisjameshall
Copy link

This change does seem to have introduced a new problem. While I could open another issue (and it could relate to the issue that @danjohnso has raised), I'm pretty sure it is closely related to this one.

The repro code given in the initial issue description shows the problem. Now, the line after the comment "This would throw ComponentNotRegisteredException" no longer throws, which I understand was the objective. However, the line after the comment "This would resolve nicely" does throw an exception (so, of course, you have to remove it or step over it to get to the working line). I don't believe that the intent was for that line to no longer resolve the registration, was it?

The exception is a different one:

System.InvalidOperationException HResult=0x80131509 Message=This operation is only valid on generic types. Source=System.Private.CoreLib StackTrace: at System.RuntimeType.GetGenericTypeDefinition() at Autofac.Util.TypeExtensions.ParameterCompatibleWithTypeConstraint(Type parameter, Type constraint) in C:\projects\autofac\src\Autofac\Util\TypeExtensions.cs:line 195 at Autofac.Util.TypeExtensions.<>c__DisplayClass7_1.<IsCompatibleWithGenericParameterConstraints>b__1(Type constraint) in C:\projects\autofac\src\Autofac\Util\TypeExtensions.cs:line 83 at System.Linq.Enumerable.Any[TSource](IEnumerable1 source, Func2 predicate) at Autofac.Util.TypeExtensions.IsCompatibleWithGenericParameterConstraints(Type genericTypeDefinition, Type[] parameters) in C:\projects\autofac\src\Autofac\Util\TypeExtensions.cs:line 81 at Autofac.Features.OpenGenerics.OpenGenericServiceBinder.TryBindServiceType(Service service, IEnumerable1 configuredOpenGenericServices, Type openGenericImplementationType, Type& constructedImplementationType, Service[]& constructedServices) in C:\projects\autofac\src\Autofac\Features\OpenGenerics\OpenGenericServiceBinder.cs:line 57
at Autofac.Features.OpenGenerics.OpenGenericRegistrationSource.d__3.MoveNext() in C:\projects\autofac\src\Autofac\Features\OpenGenerics\OpenGenericRegistrationSource.cs:line 64
at Autofac.Core.Registration.ComponentRegistry.GetInitializedServiceInfo(Service service) in C:\projects\autofac\src\Autofac\Core\Registration\ComponentRegistry.cs:line 360
at Autofac.Core.Registration.ComponentRegistry.TryGetRegistration(Service service, IComponentRegistration& registration) in C:\projects\autofac\src\Autofac\Core\Registration\ComponentRegistry.cs:line 128
at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable1 parameters, Object& instance) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 1035 at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 877
at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 347 at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 300
at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 284
at AutofacIssue.Program.Main(String[] args) in D:\source\investorist\Neo\api\AutofacIssue\Program.cs:line 16`

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

No branches or pull requests

4 participants