khellang/Scrutor

Decorator registration fails with 5.0.1 and works with 4.1.0

Opened this issue · 2 comments

This open generics registration worked with 4.1.0:

foreach (var serviceType in typeof(ICommandHandler<>).Assembly.GetTypes())
{
    if (serviceType.IsAbstract || serviceType.IsInterface || serviceType.BaseType == null)
    {
        continue;
    }

    foreach (var interfaceType in serviceType.GetInterfaces())
    {
        if (interfaceType.IsGenericType && typeof(ICommandHandler<>).IsAssignableFrom(interfaceType.GetGenericTypeDefinition()))
        {
            serviceCollection.AddScoped(interfaceType, serviceType);
            break;
        }
    }
}

serviceCollection.Decorate(typeof(ICommandHandler<>), typeof(CommandLoggingDecorator<>));

With any of the 4.2.x releases (after the big decoration PR) I'm seeing this exception:

System.ArgumentException: Cannot instantiate implementation type 'My.CommandLoggingDecorator`1[TCommand]' for service type 'Type: ICommandHandler`1+Decorated'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.Populate()
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.Hosting.HostApplicationBuilder.Build()
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
   at Program.<Main>$(String[] args) in Program.cs:line 125

Now I've tried my luck again with 5.0.1 but am getting this exception:

Unhandled exception. System.ArgumentException: Type My.ICommandHandler`1[TCommand] contains generic parameters (Parameter 'type')
   at System.Dynamic.Utils.TypeUtils.ValidateType(Type type, String paramName, Int32 index)
   at System.Dynamic.Utils.TypeUtils.ValidateType(Type type, String paramName, Boolean allowByRef, Boolean allowPointer)
   at System.Linq.Expressions.Expression.Convert(Expression expression, Type type, MethodInfo method)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.BuildFactoryExpression(ConstructorInfo constructor, Nullable`1[] parameterMap, Expression serviceProvider, Expression factoryArgumentArray)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateFactoryInternal(Type instanceType, Type[] argumentTypes, ParameterExpression& provider, ParameterExpression& argumentArray, Expression& factoryExpressionBody)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateFactory(Type instanceType, Type[] argumentTypes)
   at Scrutor.DecorationStrategy.TypeDecorator(Type serviceType, String serviceKey, Type decoratorType)
   at Scrutor.OpenGenericDecorationStrategy.CreateDecorator(Type serviceType, String serviceKey)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.TryDecorate(IServiceCollection services, DecorationStrategy strategy)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.Decorate(IServiceCollection services, DecorationStrategy strategy)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.Decorate(IServiceCollection services, Type serviceType, Type decoratorType)
   at My.RegisterCommandHandlers(IServiceCollection serviceCollection)
   at Program.<Main>$(String[] args) in Program.cs:line 123

Is this scenario no longer supported?

If helpful I can provide a self-contained example.

I managed to get this working again by using this (much more concise) piece of code:

builder.Services.Scan(scan => scan
    .FromAssemblyOf<SomeTypeInTheRightAssembly>()
    .AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>)))
    .AsImplementedInterfaces()
    .WithTransientLifetime());

builder.Services.Decorate(typeof(ICommandHandler<>), typeof(CommandLoggingDecorator<>));

But I decided to keep the issue open because it shows a breaking change nevertheless.

Hmm. I'm not sure whats going on there. It seems like maybe it's registering a generic type without all the geric type arguments?

Anyway, your second working code is what I'd call idiomatic registration code using Scrutor, so if it works, that's good 🤪