Make .NET Core DI auto resolve class by generic in

2019-03-02 11:56发布

问题:

Is there a way in .NET Core to register a generic interface, and make it resolve a class that matches a certain implementation.

For example, I have the following interface:

public interface IMapper<TFrom, TTo>
{
}

I also have an abstract class:

public abstract class Mapper<TFrom, TTo> : IMapper<TFrom, TTo>
{
    protected Mapper()
    {
        // some generic stuff
    }

    public abstract TTo Map(TFrom);
}

I can then create an implementation like so:

public class UserMapper : Mapper<Domain.User, Entity.User>
{
    public override Entity.User Map(Domain.User from)
    {
        // do mapping
    }
}

Is there a way, using the default .NET Core DI to register IMapper<,>, and let it auto resolve the class?

So if I for example would do this somewhere in code:

class SomeClass
{
    public SomeClass(IMapper<Domain.User, Entity.User> mapper) {}
}

That it somehow knows that it should resolve the class UserMapper<Domain.User, Entity.User>?

The reason is that it's a little verbose to manually register each and every mapper, specific to an implementation. So I'm hoping Microsoft.DependencyInjection is smart enough to automatically resolve its implementation somehow.

回答1:

The only way with your current design is to use Reflection:

Assembly assembly = typeof(UserMapper).Assembly;

foreach (var type in assembly.GetTypes()
    .Where(t => t.IsClass && !t.IsAbstract))
{
    foreach (var i in type.GetInterfaces())
    {
        if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapper<,>))
        {
            // NOTE: Due to a limitation of Microsoft.DependencyInjection we cannot 
            // register an open generic interface type without also having an open generic 
            // implementation type. So, we convert to a closed generic interface 
            // type to register.
            var interfaceType = typeof(IMapper<,>).MakeGenericType(i.GetGenericArguments());
            services.AddTransient(interfaceType, type);
        }
    }
}

NOTE: You could make it simpler by creating an extension method on IServiceCollection with the various overloads for AddTransient, AddSingleton, etc.

If you change the design use a non-abstract generic as your implementation type:

public class Mapper<TFrom, TTo> : IMapper<TFrom, TTo>
{
    //...
}

Then you can simply register like this:

services.AddTransient(typeof(IMapper<,>), typeof(Mapper<,>));


回答2:

Here is a more generic solutions (default scoped lifetime)

Create this extension method first

  public static void RegisterAllTypes(this IServiceCollection services, Assembly assembly, Type baseType,
            ServiceLifetime lifetime = ServiceLifetime.Scoped)
        {
            foreach (var type in assembly.GetTypes()
                .Where(t => t.IsClass && !t.IsAbstract))
            {
                foreach (var i in type.GetInterfaces()
                    .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == baseType))
                {

                    var interfaceType = baseType.MakeGenericType(i.GetGenericArguments());
                    services.Add(new ServiceDescriptor(interfaceType, type, lifetime));
                }
            }
        }

Then add this to startup.cs

 services.RegisterAllTypes(typeof(AClass).Assembly, typeof(IMapper<>));