khellang / Scrutor

Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
MIT License
3.57k stars 233 forks source link

Could not find any registered services for type #207

Open NeoElitech opened 1 year ago

NeoElitech commented 1 year ago

Hi @khellang, I'm using Scrutor to decorate an open generic type but I receive the following exception. Could not find any registered services for type 'DecoratorDemo.ICommandHandler<TCommand> I found a similar issue. But it's fixed already. Here is the link to the repository I'm using .NET.4.8 and Scrutor.4.2.2

NeoElitech commented 1 year ago
namespace DecoratorDemo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var container = new Container();
            var serviceProvider = new ServiceCollection()
                .AddSimpleInjector(container)
                .AddServices()
                .BuildServiceProvider()
                .UseSimpleInjector(container);

            var command = serviceProvider.GetRequiredService<ICommandHandler<FooCommand>>();
            command.Handle(new FooCommand());
        }
    }

    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddServices(this IServiceCollection services)
        {
            // *********** Can NOT Decorate ***********
            return services
                .AddTransient(typeof(ICommandHandler<>), typeof(CommandHandler<>))
                .Decorate(typeof(ICommandHandler<>), typeof(CommandHandlerDecorator<>));

            // *********** Can Decorate ***********
            //return services
            //  .AddTransient<ICommandHandler<FooCommand>, CommandHandler<FooCommand>>()
            //  .Decorate(typeof(ICommandHandler<>), typeof(CommandHandlerDecorator<>));
        }
    }
}
khellang commented 1 year ago

Hello @NeoElitech! 👋🏻

Sorry for the long reply time.

The issue with open generic decoration is that Scrutor needs to know which concrete services are registered ahead of time. It does this by inspecting the concrete/closed services already registered in the container. If you replace the first AddTransient call with something like

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

Scrutor will can the specified assembly and add all closed generic services that implements ICommandHandler<T>.

When you then call Decorate, it should be able to find the registrations and successfully decorate them 😄

coding-bunny commented 11 months ago

I'm running into a similar problem, and the suggested solution doesn't provide any help. We have an assembly with an interface, a service and service decorator. They're all in the same assembly with their own extension method to register them:

    public interface IGetJoinedDocumentsService<out TDocument, in TUniqueId>
    {
        /// <summary>
        ///     Retrieves data of type <see cref="TDocument"/> related to a specified <paramref name="uniqueId"/>. 
        /// </summary>
        /// <param name="uniqueId">An instance of <see cref="TUniqueId"/> of a to retrieve related data for.</param>
        /// <param name="cancellationToken">An instance of <see cref="CancellationToken"/> to stop enumeration.</param>
        /// <returns>A collection of <see cref="TDocument"/> with data; empty collection if there are no data.</returns>
        IAsyncEnumerable<TDocument> GetAsync(TUniqueId uniqueId, CancellationToken cancellationToken);
    }

internal class GetJoinedDocumentsService<TDocument, TUniqueId> : IGetJoinedDocumentsService<TDocument, TUniqueId> {}
public class GetDocumentEnrichmentDecorator<TDocument, TUniqueId> : IGetDocument<TDocument, TUniqueId> {}

And this throws the error:

 var assembly = typeof(IGetJoinedDocumentsService<,>).Assembly;

            serviceCollection.Scan(scan => scan
                .FromAssemblies(assembly)
                .AddClasses(classes => classes.AssignableTo(typeof(IGetJoinedDocumentsService<,>)))
                .AsImplementedInterfaces()
                .WithTransientLifetime());

            return serviceCollection
                .AddScoped(typeof(IGetJoinedDocumentsService<,>), typeof(GetJoinedDocumentsService<,>))
                .Decorate(typeof(IGetJoinedDocumentsService<,>), typeof(GetJoinedDocumentsServiceEnrichmentDecorator<,>))
Scrutor.DecorationException: Could not find any registered services for type 'BizMachine.Prospector.Modules.Filtering.Enrichment.IGetJoinedDocumentsService<TDocument, TUniqueId>'.
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.Decorate(IServiceCollection services, DecorationStrategy strategy)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.Decorate(IServiceCollection services, Type serviceType, Type decoratorType)
   at Microsoft.Extensions.DependencyInjection.EnrichmentServiceCollectionExtensions.AddFilteringEnrichmentDecorators(IServiceCollection serviceCollection) in D:\src\bm-products-api\Modules.Filtering.Enrichment\EnrichmentServiceCollectionExtensions.cs:line 23
   at Program.<>c.<<Main>$>b__0_0(HostBuilderContext context, IServiceCollection services) in D:\src\bm-products-api\Modules.Filtering.ElasticSearch.Console\Program.cs:line 21
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at Program.<Main>$(String[] args) in D:\src\bm-products-api\Modules.Filtering.ElasticSearch.Console\Program.cs:line 12
   at Program.<Main>(String[] args)
savornicesei commented 7 months ago

I believe this is caused by #39 and can't be fixed as things are today were in 2017. IT seems by this SO thread that there are ways to do it with the MS DI.