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.
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<,>));
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<>));