Why cannot build ServiceProvider when using Scrutor and registering an open generic type as implemented interface

111 Views Asked by At

Please help me to understand why the following code works and why do I get System.ArgumentException: 'Cannot instantiate implementation type 'Concrete1[TOther]' for service type 'IInterface1[System.String]'.' if I uncomment the only commented line.

using Microsoft.Extensions.DependencyInjection.Extensions;
using Scrutor;

public interface IInterface { }

public interface IInterface<T> where T : class { }

public abstract class AbstractClass<T> : IInterface<T>, IInterface where T : class { }

public class Concrete<TOther> : AbstractClass<string> { }

internal class Program
{
    private static void Main(string[] args)
    {
        var services = new ServiceCollection();

        services.Scan(x =>
        {
            x.FromApplicationDependencies(a => true)
                .AddClasses(t => t.AssignableTo(typeof(IInterface<>)))
                .UsingRegistrationStrategy(RegistrationStrategy.Replace())
                //.As(type => type.GetInterfaces().Where(v => v.IsGenericType && v.GetGenericArguments().Length == 1).ToArray())
                .AsSelf()
                .WithLifetime(ServiceLifetime.Scoped);
        });

        var sp = services.BuildServiceProvider();
    }
}
1

There are 1 best solutions below

7
Guru Stron On

Concrete<TOther> is a generic type which can't be constructed without passing generic type parameter, i.e. something like Concrete<string> which can't be done by the DI. Basically your registration will be analogous to:

services.AddScoped(typeof(IInterface<string>), typeof(Concrete<>));

So when user will resolve IInterface<string> (sp.CreateScope().ServiceProvider.GetService<IInterface<string>>()) the provider should be able to do something like new Concrete<> which obviously is not possible.

Build-in DI container allows registering open generic services with open generic implementations, something like:

services.AddScoped(typeof(Concrete<>));
services.AddScoped(typeof(IInterface<>), typeof(Concrete<>));

But this requires concrete to be declared as:

public class Concrete<TOther> : AbstractClass<TOther> where TOther : class { }

If it suits your needs then you can mimic that with Scrutor next way:

services.Scan(x =>
{
    x.FromApplicationDependencies(a => true)
        .AddClasses(t => t.AssignableTo(typeof(IInterface<>)))
        .UsingRegistrationStrategy(RegistrationStrategy.Replace())
        .As(type => type.GetInterfaces()
            .Where(v => v.IsGenericType && v.GetGenericArguments().Length == 1)
            .Select(t => t.GetGenericTypeDefinition())
            .ToArray()) // or just .As(typeof(IInterface<>))
        .AsSelf()
        .WithLifetime(ServiceLifetime.Scoped);
});

I just did not understand why the commented line if uncommented broke the program. I guess I got it, thank you for clarification.

It breaks the program because it tries to register closed generic service IInterface<string> as open generic type which is impossible.