.NET Core中参数数量不同的原生泛型自动注册方案咨询
Hey there! Let's work through this generic service registration challenge you're dealing with in your .NET Core project. I’ve tackled similar scenarios when building adaptation layers between entities and DTOs, so I think I can point you in the right direction.
From what I gather, you have a base IAdaptable<TEntity, TDTO> as a convenience wrapper, but your team prefers using custom interfaces (that likely inherit from or align with IAdaptable<,>) to control property mapping logic. You want a way to register these adaptation classes generically, without manually adding each one to your DI container.
Here are a few practical approaches tailored to common scenarios:
1. Batch Register Custom Adaptation Implementations
If your custom adaptation interfaces either inherit directly from IAdaptable<TEntity, TDTO> or your implementation classes implement both the custom interface and IAdaptable<,>, you can use reflection to scan your assembly and auto-register them.
First, target the assembly where your adaptation classes live:
// In Program.cs (.NET 6+) or Startup.cs var adaptationAssembly = typeof(UserAdaptable).Assembly; // Replace with one of your adaptation classes
Then scan for all non-abstract, non-interface types that implement any IAdaptable<,>-based interface, and register each against its corresponding interface:
var adaptableTypes = adaptationAssembly.GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface) .SelectMany(t => t.GetInterfaces() .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAdaptable<,>)) .Select(i => new { ServiceInterface = i, Implementation = t })); foreach (var typePair in adaptableTypes) { services.AddScoped(typePair.ServiceInterface, typePair.Implementation); }
If you use custom-named interfaces (like IUserAdaptable instead of a generic sub-interface), you can adjust the filter to include those too—for example, by checking if the interface name ends with "Adaptable":
var adaptableTypes = adaptationAssembly.GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface) .Select(t => new { Implementation = t, Interfaces = t.GetInterfaces() // Include generic IAdaptable<,> interfaces .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAdaptable<,>)) // Include custom non-generic adaptation interfaces .Concat(t.GetInterfaces().Where(i => !i.IsGenericType && i.Name.EndsWith("Adaptable"))) }) .Where(x => x.Interfaces.Any()); foreach (var typeSet in adaptableTypes) { foreach (var iface in typeSet.Interfaces) { services.AddScoped(iface, typeSet.Implementation); } }
2. Fallback to Default IAdaptable<,> Implementation
If you want to use custom adaptations where they exist, and fall back to a default DefaultAdaptable<TEntity, TDTO> when no custom logic is needed, combine the batch registration with an open-generic registration:
// First register all custom adaptations (from step 1) // ... // Then register the default open-generic implementation services.AddScoped(typeof(IAdaptable<,>), typeof(DefaultAdaptable<,>));
The DI container will prioritize specific registered implementations (like your IUserAdaptable or IAdaptable<User, UserDTO>) over the open-generic one, so you get the best of both worlds.
3. Attribute-Based Registration (For Extra Flexibility)
To avoid relying on naming conventions or interface inheritance, you can mark your adaptation classes with a custom attribute, then scan for those attributes.
First, define the attribute:
[AttributeUsage(AttributeTargets.Class)] public class AdaptationServiceAttribute : Attribute { }
Mark your adaptation classes:
[AdaptationService] public class UserAdaptable : IUserAdaptable, IAdaptable<User, UserDTO> { // Your mapping logic here }
Then adjust the scanning logic to target classes with this attribute:
var adaptableTypes = adaptationAssembly.GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface && t.GetCustomAttribute<AdaptationServiceAttribute>() != null) .SelectMany(t => t.GetInterfaces() .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAdaptable<,>)) .Select(i => new { ServiceInterface = i, Implementation = t })); foreach (var typePair in adaptableTypes) { services.AddScoped(typePair.ServiceInterface, typePair.Implementation); }
Quick Notes to Avoid Headaches
- Double-check that you’re scanning the correct assembly—if your adaptations live in a separate class library, reference that assembly instead of your web project.
- If your adaptation classes have constructor dependencies, make sure those dependencies are already registered in the DI container.
- For .NET 6+, all this logic goes right into your
Program.csalongside other service registrations.
Hope this helps you automate that registration process and keep your DI setup clean!
内容的提问来源于stack exchange,提问作者johnny 5

