如何减少相似依赖注入定义?用反射优化批量注入代码
Great question! Manually registering every IHandler<T> and its corresponding implementation gets really tedious as your application grows—no one wants to write that repetitive AddTransient line dozens of times. Let’s break down a few solid approaches to clean this up:
1. Manual Reflection-Based Registration
You can use .NET’s reflection APIs to scan your assembly for all handler implementations and register them automatically. Here’s how to do it:
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); // Get the assembly where your handlers/MyClass types live var targetAssembly = typeof(MyClass1).Assembly; // Find all non-abstract, non-interface types that implement IHandler<> var handlerPairs = targetAssembly.GetTypes() .Where(type => !type.IsAbstract && !type.IsInterface) .Select(type => new { ImplementationType = type, HandlerInterface = type.GetInterfaces() .FirstOrDefault(iface => iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IHandler<>)) }) .Where(pair => pair.HandlerInterface != null); // Register each handler-transient pair foreach (var pair in handlerPairs) { services.AddTransient(pair.HandlerInterface, pair.ImplementationType); } }
Notes:
- If your handlers are spread across multiple assemblies, adjust
targetAssemblyto include those (e.g.,Assembly.Load("MyHandlersAssembly")). - This approach fully automates registration—add a new handler class, and it’ll get picked up without modifying this method.
2. Use the Scrutor Library (Simpler Reflection Wrapper)
Scrutor is a popular NuGet package that simplifies bulk dependency registration by wrapping reflection logic in a clean, fluent API. It’s less error-prone than writing raw reflection yourself.
First, install the package via NuGet:
Install-Package Scrutor # Or via .NET CLI: dotnet add package Scrutor
Then update your ConfigureServices method:
using Scrutor; public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); // Scan your assembly and register all IHandler<> implementations as transient services.Scan(scan => scan .FromAssemblyOf<MyClass1>() // Target the assembly with your handlers .AddClasses(classes => classes.AssignableTo(typeof(IHandler<>))) // Filter handler classes .AsImplementedInterfaces() // Register each class against its implemented interfaces .WithTransientLifetime()); // Set the service lifetime }
Why Scrutor?
- It handles edge cases (like types implementing multiple interfaces) gracefully.
- You can easily adjust lifetimes (swap
WithTransientLifetimeforWithScopedLifetimeorWithSingletonLifetime). - The fluent syntax makes the code readable and maintainable.
3. Manual Mapping Dictionary (For Controlled Registration)
If you prefer to avoid reflection entirely but still want to reduce repetitive code, you can centralize your handler mappings in a dictionary:
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); var handlerMappings = new Dictionary<Type, Type> { { typeof(IHandler<MyClass1>), typeof(AHandler) }, { typeof(IHandler<MyClass2>), typeof(BHandler) }, { typeof(IHandler<MyClass3>), typeof(CHandler) }, // Add new mappings here as needed }; foreach (var (interfaceType, implementationType) in handlerMappings) { services.AddTransient(interfaceType, implementationType); } }
Pros:
- No reflection overhead (minimal, but worth noting).
- Full control over which handlers get registered—you won’t accidentally register unintended types.
Final Recommendation
If you want full automation and don’t mind adding a lightweight NuGet package, Scrutor is the way to go—it’s the most clean and maintainable option. If you can’t add third-party packages, the raw reflection approach works great too. The dictionary method is best for small apps where you want explicit control over registrations.
内容的提问来源于stack exchange,提问作者ca9163d9

