Autofac: Resolving services registered as open generics with generic constraints throws ComponentNotRegisteredException

Created on 27 Mar 2019  路  5Comments  路  Source: autofac/Autofac

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
bug help wanted

Most helpful comment

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.

All 5 comments

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.

This gets fixed by #978. Yay!

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)

@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!

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.ResolveTService 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`

Was this page helpful?
0 / 5 - 0 ratings