Reputation: 21047
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.
Upvotes: 3
Views: 4874
Reputation: 5855
Here you have ready to use helpers:
by Generic interfaces
services.AddAllGenericTypes(typeof(IGenericRepository<>), new[] {typeof(MyDbContext).GetTypeInfo().Assembly});
By Interface
services.AddAllTypes<IGenerator>(new[] { typeof(FileHandler).GetTypeInfo().Assembly },
With extension from: https://gist.github.com/GetoXs/5caf0d8cfe6faa8a855c3ccef7c5a541
Upvotes: 1
Reputation: 2654
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<>));
Upvotes: 3
Reputation: 56869
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 forAddTransient
,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<,>));
Upvotes: 8